From 2fb62bd04aa5319e89c79e923f62e17d691c1770 Mon Sep 17 00:00:00 2001 From: mhmmdjafarg Date: Fri, 26 Dec 2025 13:49:10 +0700 Subject: [PATCH 1/7] bybit functionality --- .env.example | 3 + .gitignore | 1 - playground.ipynb | 290 ++++++++++++++++++++++++ tradingagents/dataflows/bybit.py | 363 +++++++++++++++++++++++++++++++ tradingagents/default_config.py | 7 +- 5 files changed, 662 insertions(+), 2 deletions(-) create mode 100644 playground.ipynb create mode 100644 tradingagents/dataflows/bybit.py diff --git a/.env.example b/.env.example index 3a6cffdf..3c6f6dd4 100644 --- a/.env.example +++ b/.env.example @@ -5,3 +5,6 @@ TELEGRAM_API_HASH=telegram_api_hash_placeholder TELEGRAM_SESSION_NAME=telegram_session_name_placeholder BINANCE_API_KEY=binance_api_key_placeholder TAAPI_API_KEY=taapi_api_key_placeholder +BYBIT_BASE_URL=https://api-demo.bybit.com +BYBIT_API_KEY=bybit_api_key_placeholder +BYBIT_API_SECRET=bybit_api_secret_placeholder diff --git a/.gitignore b/.gitignore index 280b872e..3b1f744b 100644 --- a/.gitignore +++ b/.gitignore @@ -10,5 +10,4 @@ eval_data/ *.egg-info/ .env .python-version -playground.ipynb *session diff --git a/playground.ipynb b/playground.ipynb new file mode 100644 index 00000000..a8cb75ce --- /dev/null +++ b/playground.ipynb @@ -0,0 +1,290 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "ab4c0304", + "metadata": {}, + "outputs": [], + "source": [ + "from tradingagents.dataflows.binance import get_market_data\n", + "\n", + "print(get_market_data('BTC/USDT', '2025-12-20', '2025-12-25'))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a58bb868", + "metadata": {}, + "outputs": [], + "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))" + ] + }, + { + "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", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "5000.62453368\n" + ] + } + ], + "source": [ + "from tradingagents.dataflows.bybit import get_wallet_balance\n", + "balance = get_wallet_balance('USDT')\n", + "print(balance)" + ] + }, + { + "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", + "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" + ] + } + ], + "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)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tradingagents/dataflows/bybit.py b/tradingagents/dataflows/bybit.py new file mode 100644 index 00000000..d137001d --- /dev/null +++ b/tradingagents/dataflows/bybit.py @@ -0,0 +1,363 @@ +import hashlib +import hmac +import json +import time +from typing import Dict, Optional +from urllib.parse import urlencode + +import requests +from .config import get_config + + +def bybit_v5_request(method: str, path: str, params: Optional[Dict] = None, body: Optional[Dict] = None) -> Dict: + """Generic signed HTTP request helper for Bybit V5 API.""" + config = get_config()["external"] + base_url = config["BYBIT_BASE_URL"].rstrip("/") + api_key = config["BYBIT_API_KEY"] + api_secret = config["BYBIT_API_SECRET"] + + if not api_key or not api_secret: + raise ValueError("Missing BYBIT_API_KEY or BYBIT_API_SECRET") + + timestamp = str(int(time.time() * 1000)) + recv_window = "5000" + + # Build query string for GET or body for POST + if method.upper() == "GET" and params: + query_string = urlencode(sorted(params.items())) + url = f"{base_url}{path}?{query_string}" + payload = query_string + else: + url = f"{base_url}{path}" + payload = json.dumps(body, separators=(',', ':')) if body else "" + + # Create signature + sign_payload = f"{timestamp}{api_key}{recv_window}{payload}" + signature = hmac.new( + api_secret.encode('utf-8'), + sign_payload.encode('utf-8'), + hashlib.sha256 + ).hexdigest() + + # Headers + headers = { + "X-BAPI-API-KEY": api_key, + "X-BAPI-TIMESTAMP": timestamp, + "X-BAPI-RECV-WINDOW": recv_window, + "X-BAPI-SIGN": signature, + "Content-Type": "application/json" + } + + # Make request + if method.upper() == "GET": + response = requests.get(url, headers=headers) + else: + response = requests.post(url, headers=headers, data=payload) + + response.raise_for_status() + data = response.json() + + if data.get("retCode") != 0: + raise ValueError(f"Bybit API error: {data.get('retMsg')}") + + return data + + +def get_wallet_balance(coin: str, account_type: str = "UNIFIED") -> float: + """Fetch wallet balance for a specific coin from Bybit.""" + data = bybit_v5_request("GET", "/v5/account/wallet-balance", { + "accountType": account_type, + "coin": coin.upper() + }) + + # Parse response to get wallet balance + result = data.get("result", {}) + accounts = result.get("list", []) + + if not accounts: + return 0.0 + + coins = accounts[0].get("coin", []) + for coin_data in coins: + if coin_data.get("coin") == coin.upper(): + return float(coin_data.get("walletBalance", 0)) + + return 0.0 + + +def get_order_status(order_id: str, category: str = "spot") -> Dict: + """ + Get order status by order ID. + + Args: + order_id: Order ID to query + category: Trading category ("spot", "linear", "inverse") + + Returns: + Dict containing order information + """ + params = { + "category": category.lower(), + "orderId": order_id + } + + data = bybit_v5_request("GET", "/v5/order/realtime", params=params) + result = data.get("result", {}) + orders = result.get("list", []) + + return orders[0] if orders else {} + + +def cancel_order(order_id: str, symbol: str, category: str = "spot") -> Dict: + """ + Cancel an existing order. + + Args: + order_id: Order ID to cancel + symbol: Trading pair symbol + category: Trading category ("spot", "linear", "inverse") + + Returns: + Dict containing cancellation result + """ + body = { + "category": category.lower(), + "symbol": symbol.upper(), + "orderId": order_id + } + + data = bybit_v5_request("POST", "/v5/order/cancel", body=body) + 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", + limit: int = 20 +) -> Dict: + """ + Get order history. + + Args: + symbol: Optional trading pair to filter by + category: Trading category ("spot", "linear", "inverse") + limit: Number of records to return (max 50) + + Returns: + Dict containing order history + """ + params = { + "category": category.lower(), + "limit": min(limit, 50) + } + + if symbol: + params["symbol"] = symbol.upper() + + data = bybit_v5_request("GET", "/v5/order/history", params=params) + return data.get("result", {}) + + +def get_account_info(account_type: str = "UNIFIED") -> Dict: + """ + Get account information. + + Args: + account_type: Account type ("UNIFIED") + + Returns: + Dict containing account information + """ + params = { + "accountType": account_type + } + + data = bybit_v5_request("GET", "/v5/account/info", params=params) + return data.get("result", {}) + +def place_order( + symbol: str, + side: str, + order_type: str, + qty: float, + price: Optional[float] = None, + stop_loss: Optional[float] = None, + take_profit: Optional[float] = None, + sl_limit_price: Optional[float] = None, + tp_limit_price: Optional[float] = None, + sl_order_type: str = "Market", + tp_order_type: str = "Market", + time_in_force: str = "GTC", + account_type: str = "UNIFIED", + category: str = "spot", + order_link_id: Optional[str] = None, + reduce_only: bool = False, + close_on_trigger: bool = False +) -> Dict: + """ + Place an order on Bybit with comprehensive support for spot trading. + + Args: + symbol: Trading pair symbol (e.g., "BTCUSDT") + side: Order side ("Buy" or "Sell") + order_type: Order type ("Market", "Limit") + qty: Order quantity + price: Order price (required for Limit orders) + stop_loss: Stop loss trigger price + take_profit: Take profit trigger price + sl_limit_price: Stop loss limit price (for limit SL orders) + tp_limit_price: Take profit limit price (for limit TP orders) + sl_order_type: Stop loss order type ("Market" or "Limit") + tp_order_type: Take profit order type ("Market" or "Limit") + time_in_force: Time in force ("GTC", "IOC", "FOK", "PostOnly") + account_type: Account type ("UNIFIED") + category: Trading category ("spot", "linear", "inverse") + order_link_id: Custom order ID + reduce_only: Reduce only flag + close_on_trigger: Close on trigger flag + + Returns: + Dict containing order result + + Raises: + ValueError: If required parameters are missing or invalid + """ + # Validate required parameters + if not symbol or not side or not order_type: + raise ValueError("symbol, side, and order_type are required") + + if qty <= 0: + raise ValueError("qty must be greater than 0") + + # Validate order type and price requirement + if order_type.upper() == "LIMIT" and price is None: + raise ValueError("price is required for Limit orders") + + # Validate side + if side.upper() not in ["BUY", "SELL"]: + raise ValueError("side must be 'Buy' or 'Sell'") + + # Validate order type + valid_order_types = ["MARKET", "LIMIT"] + if order_type.upper() not in valid_order_types: + raise ValueError(f"order_type must be one of {valid_order_types}") + + # Build order body + body = { + "category": category.lower(), + "symbol": symbol.upper(), + "side": side.capitalize(), + "orderType": order_type.capitalize(), + "qty": str(qty), + "timeInForce": time_in_force, + } + + # Add price for limit orders + if price is not None: + body["price"] = str(price) + + # Add stop loss with proper formatting + if stop_loss is not None: + body["stopLoss"] = str(stop_loss) + body["slOrderType"] = sl_order_type.capitalize() + + # Add limit price for stop loss if specified + if sl_order_type.upper() == "LIMIT" and sl_limit_price is not None: + body["slLimitPrice"] = str(sl_limit_price) + + # Add take profit with proper formatting + if take_profit is not None: + body["takeProfit"] = str(take_profit) + body["tpOrderType"] = tp_order_type.capitalize() + + # Add limit price for take profit if specified + if tp_order_type.upper() == "LIMIT" and tp_limit_price is not None: + body["tpLimitPrice"] = str(tp_limit_price) + + # Add optional parameters + if order_link_id: + body["orderLinkId"] = order_link_id + + if reduce_only: + body["reduceOnly"] = True + + if close_on_trigger: + body["closeOnTrigger"] = True + + try: + data = bybit_v5_request("POST", "/v5/order/create", body=body) + return data.get("result", {}) + except Exception as e: + raise ValueError(f"Failed to place order: {str(e)}") + +def place_spot_order_with_sl_tp( + symbol: str, + side: str, + qty: float, + price: Optional[float] = None, + stop_loss_price: Optional[float] = None, + take_profit_price: Optional[float] = None, + sl_limit_price: Optional[float] = None, + tp_limit_price: Optional[float] = None, + sl_order_type: str = "Market", + tp_order_type: str = "Market", + order_type: str = "Limit", + time_in_force: str = "PostOnly" +) -> Dict: + """ + Convenience function to place a spot order with stop loss and take profit. + + Args: + symbol: Trading pair symbol (e.g., "BTCUSDT") + side: Order side ("Buy" or "Sell") + qty: Order quantity + price: Limit price (None for market orders) + stop_loss_price: Stop loss trigger price + take_profit_price: Take profit trigger price + sl_limit_price: Stop loss limit price (for limit SL orders) + tp_limit_price: Take profit limit price (for limit TP orders) + sl_order_type: Stop loss order type ("Market" or "Limit") + tp_order_type: Take profit order type ("Market" or "Limit") + order_type: "Limit" or "Market" + time_in_force: Time in force ("GTC", "IOC", "FOK", "PostOnly") + + Returns: + Dict containing order result + """ + return place_order( + symbol=symbol, + side=side, + order_type=order_type, + qty=qty, + price=price, + stop_loss=stop_loss_price, + take_profit=take_profit_price, + sl_limit_price=sl_limit_price, + tp_limit_price=tp_limit_price, + sl_order_type=sl_order_type, + tp_order_type=tp_order_type, + time_in_force=time_in_force, + category="spot" + ) diff --git a/tradingagents/default_config.py b/tradingagents/default_config.py index e5b304f8..419abb06 100644 --- a/tradingagents/default_config.py +++ b/tradingagents/default_config.py @@ -33,6 +33,11 @@ DEFAULT_CONFIG = { }, # Tool provider settings "tool_providers": { - "TAAPI_BASE_URL": "https://api.taapi.io", + "TAAPI_BASE_URL": os.getenv("TAAPI_BASE_URL", "https://api.taapi.io"), + }, + "external": { + "BYBIT_BASE_URL": os.getenv("BYBIT_BASE_URL", "https://api-demo.bybit.com"), + "BYBIT_API_KEY": os.getenv("BYBIT_API_KEY", ""), + "BYBIT_API_SECRET": os.getenv("BYBIT_API_SECRET", ""), } } From 35c707ff93f262396c2cbb0f7619f5b31e7d3b9c Mon Sep 17 00:00:00 2001 From: Faris Hasim Date: Fri, 26 Dec 2025 14:21:25 +0700 Subject: [PATCH 2/7] update prompt to propose quantity --- .python-version | 1 - tradingagents/agents/trader/trader.py | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) delete mode 100644 .python-version diff --git a/.python-version b/.python-version deleted file mode 100644 index 56bb6605..00000000 --- a/.python-version +++ /dev/null @@ -1 +0,0 @@ -3.12.7 diff --git a/tradingagents/agents/trader/trader.py b/tradingagents/agents/trader/trader.py index 4135cab7..e58e5eae 100644 --- a/tradingagents/agents/trader/trader.py +++ b/tradingagents/agents/trader/trader.py @@ -39,7 +39,9 @@ def create_trader(llm, memory): messages = [ { "role": "system", - "content": f"""You are a crypto trading agent analyzing cryptocurrency market data for a specific trading pair (e.g., BTC/USDT). Based on your analysis, provide a specific recommendation to BUY, SELL, or HOLD the base asset relative to the quote asset for the pair {pair_context}. End with a firm decision and always conclude your response with 'FINAL TRANSACTION PROPOSAL: **BUY/HOLD/SELL**' to confirm your recommendation. Do not forget to utilize lessons from past decisions to learn from your mistakes. Here is some reflections from similar situations you traded in and the lessons learned: {past_memory_str}""", + "content": f"""You are a crypto trading agent analyzing cryptocurrency market data for a specific trading pair (e.g., BTC/USDT). Based on your analysis, provide a specific recommendation to BUY, SELL, or HOLD the base asset relative to the quote asset for the pair {pair_context}, along with the quantity for BUY and SELL \ + End with a firm decision and always conclude your response with 'FINAL TRANSACTION PROPOSAL: **BUY/HOLD/SELL** **QUANTITY**' to confirm your recommendation. \ + Do not forget to utilize lessons from past decisions to learn from your mistakes. Here is some reflections from similar situations you traded in and the lessons learned: {past_memory_str}""", }, context, ] From 72061a8ae225876617514e34311e256c2dda9c53 Mon Sep 17 00:00:00 2001 From: mhmmdjafarg Date: Mon, 29 Dec 2025 17:09:52 +0700 Subject: [PATCH 3/7] cleanup company of interest and unused import --- .../agents/analysts/fundamentals_analyst.py | 4 ---- .../agents/analysts/market_analyst.py | 20 +++++++++---------- tradingagents/agents/analysts/news_analyst.py | 4 ---- .../agents/analysts/social_media_analyst.py | 4 ---- .../agents/managers/research_manager.py | 6 ++---- tradingagents/agents/managers/risk_manager.py | 8 ++------ .../agents/researchers/bear_researcher.py | 9 +++------ .../agents/researchers/bull_researcher.py | 7 ++----- .../agents/risk_mgmt/aggresive_debator.py | 4 ---- .../agents/risk_mgmt/conservative_debator.py | 5 ----- .../agents/risk_mgmt/neutral_debator.py | 4 ---- tradingagents/agents/trader/trader.py | 3 --- tradingagents/agents/utils/agent_states.py | 10 +++------- tradingagents/graph/propagation.py | 1 - tradingagents/graph/trading_graph.py | 2 +- 15 files changed, 23 insertions(+), 68 deletions(-) diff --git a/tradingagents/agents/analysts/fundamentals_analyst.py b/tradingagents/agents/analysts/fundamentals_analyst.py index d1fdd3d8..2a21a457 100644 --- a/tradingagents/agents/analysts/fundamentals_analyst.py +++ b/tradingagents/agents/analysts/fundamentals_analyst.py @@ -1,15 +1,11 @@ from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder -import time -import json from tradingagents.agents.utils.agent_utils import get_fundamentals, get_whitepaper, get_market_cap -from tradingagents.dataflows.config import get_config def create_fundamentals_analyst(llm): def fundamentals_analyst_node(state): current_date = state["trade_date"] ticker = state["ticker_of_interest"] - company_name = state["ticker_of_interest"] tools = [ get_fundamentals, diff --git a/tradingagents/agents/analysts/market_analyst.py b/tradingagents/agents/analysts/market_analyst.py index 8f54b988..95f80581 100644 --- a/tradingagents/agents/analysts/market_analyst.py +++ b/tradingagents/agents/analysts/market_analyst.py @@ -16,20 +16,20 @@ def create_market_analyst(llm): 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: + - 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. -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 (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. -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 (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. -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: + - 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. -- 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.""" + - 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.""" + """ 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/news_analyst.py b/tradingagents/agents/analysts/news_analyst.py index 64ddef4c..d8137acc 100644 --- a/tradingagents/agents/analysts/news_analyst.py +++ b/tradingagents/agents/analysts/news_analyst.py @@ -1,9 +1,5 @@ from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder -import time -import json from tradingagents.agents.utils.agent_utils import get_news, get_global_news -from tradingagents.dataflows.config import get_config - def create_news_analyst(llm): def news_analyst_node(state): diff --git a/tradingagents/agents/analysts/social_media_analyst.py b/tradingagents/agents/analysts/social_media_analyst.py index cf2ccad0..269c6c10 100644 --- a/tradingagents/agents/analysts/social_media_analyst.py +++ b/tradingagents/agents/analysts/social_media_analyst.py @@ -1,15 +1,11 @@ from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder -import time -import json from tradingagents.agents.utils.agent_utils import get_news, get_fear_and_greed -from tradingagents.dataflows.config import get_config def create_social_media_analyst(llm): def social_media_analyst_node(state): current_date = state["trade_date"] ticker = state["ticker_of_interest"] - coin_name = state["ticker_of_interest"] tools = [ get_news, diff --git a/tradingagents/agents/managers/research_manager.py b/tradingagents/agents/managers/research_manager.py index c537fa2f..d2cddd76 100644 --- a/tradingagents/agents/managers/research_manager.py +++ b/tradingagents/agents/managers/research_manager.py @@ -1,8 +1,6 @@ -import time -import json +from tradingagents.agents.utils.memory import FinancialSituationMemory - -def create_research_manager(llm, memory): +def create_research_manager(llm, memory: FinancialSituationMemory): def research_manager_node(state) -> dict: history = state["investment_debate_state"].get("history", "") market_research_report = state["market_report"] diff --git a/tradingagents/agents/managers/risk_manager.py b/tradingagents/agents/managers/risk_manager.py index fba763d6..e251f3c5 100644 --- a/tradingagents/agents/managers/risk_manager.py +++ b/tradingagents/agents/managers/risk_manager.py @@ -1,12 +1,8 @@ -import time -import json +from tradingagents.agents.utils.memory import FinancialSituationMemory - -def create_risk_manager(llm, memory): +def create_risk_manager(llm, memory: FinancialSituationMemory): def risk_manager_node(state) -> dict: - company_name = state["company_of_interest"] - history = state["risk_debate_state"]["history"] risk_debate_state = state["risk_debate_state"] market_research_report = state["market_report"] diff --git a/tradingagents/agents/researchers/bear_researcher.py b/tradingagents/agents/researchers/bear_researcher.py index cfaf06e9..6ee5d8e3 100644 --- a/tradingagents/agents/researchers/bear_researcher.py +++ b/tradingagents/agents/researchers/bear_researcher.py @@ -1,9 +1,6 @@ -from langchain_core.messages import AIMessage -import time -import json +from tradingagents.agents.utils.memory import FinancialSituationMemory - -def create_bear_researcher(llm, memory): +def create_bear_researcher(llm, memory: FinancialSituationMemory): def bear_node(state) -> dict: investment_debate_state = state["investment_debate_state"] history = investment_debate_state.get("history", "") @@ -37,7 +34,7 @@ Resources available: Market research report: {market_research_report} Social media sentiment report: {sentiment_report} Latest world affairs news: {news_report} -Company fundamentals report: {fundamentals_report} +Coin fundamentals report: {fundamentals_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 6bc07dff..e5be2337 100644 --- a/tradingagents/agents/researchers/bull_researcher.py +++ b/tradingagents/agents/researchers/bull_researcher.py @@ -1,9 +1,6 @@ -from langchain_core.messages import AIMessage -import time -import json +from tradingagents.agents.utils.memory import FinancialSituationMemory - -def create_bull_researcher(llm, memory): +def create_bull_researcher(llm, memory: FinancialSituationMemory): def bull_node(state) -> dict: investment_debate_state = state["investment_debate_state"] history = investment_debate_state.get("history", "") diff --git a/tradingagents/agents/risk_mgmt/aggresive_debator.py b/tradingagents/agents/risk_mgmt/aggresive_debator.py index 8363b0ad..575da9f5 100644 --- a/tradingagents/agents/risk_mgmt/aggresive_debator.py +++ b/tradingagents/agents/risk_mgmt/aggresive_debator.py @@ -1,7 +1,3 @@ -import time -import json - - def create_risky_debator(llm): def risky_node(state) -> dict: risk_debate_state = state["risk_debate_state"] diff --git a/tradingagents/agents/risk_mgmt/conservative_debator.py b/tradingagents/agents/risk_mgmt/conservative_debator.py index 961a786e..c863351b 100644 --- a/tradingagents/agents/risk_mgmt/conservative_debator.py +++ b/tradingagents/agents/risk_mgmt/conservative_debator.py @@ -1,8 +1,3 @@ -from langchain_core.messages import AIMessage -import time -import json - - def create_safe_debator(llm): def safe_node(state) -> dict: risk_debate_state = state["risk_debate_state"] diff --git a/tradingagents/agents/risk_mgmt/neutral_debator.py b/tradingagents/agents/risk_mgmt/neutral_debator.py index 6a5ff5c5..efc48c3f 100644 --- a/tradingagents/agents/risk_mgmt/neutral_debator.py +++ b/tradingagents/agents/risk_mgmt/neutral_debator.py @@ -1,7 +1,3 @@ -import time -import json - - def create_neutral_debator(llm): def neutral_node(state) -> dict: risk_debate_state = state["risk_debate_state"] diff --git a/tradingagents/agents/trader/trader.py b/tradingagents/agents/trader/trader.py index e58e5eae..167c0b6a 100644 --- a/tradingagents/agents/trader/trader.py +++ b/tradingagents/agents/trader/trader.py @@ -1,7 +1,4 @@ import functools -import time -import json - def create_trader(llm, memory): def trader_node(state, name): diff --git a/tradingagents/agents/utils/agent_states.py b/tradingagents/agents/utils/agent_states.py index 4f6c7f34..b1541db3 100644 --- a/tradingagents/agents/utils/agent_states.py +++ b/tradingagents/agents/utils/agent_states.py @@ -1,10 +1,7 @@ -from typing import Annotated, Sequence -from datetime import date, timedelta, datetime -from typing_extensions import TypedDict, Optional -from langchain_openai import ChatOpenAI +from typing import Annotated +from typing_extensions import TypedDict from tradingagents.agents import * -from langgraph.prebuilt import ToolNode -from langgraph.graph import END, StateGraph, START, MessagesState +from langgraph.graph import MessagesState # Researcher team state @@ -48,7 +45,6 @@ class RiskDebateState(TypedDict): class AgentState(MessagesState): - company_of_interest: Annotated[str, "Company that we are interested in trading"] ticker_of_interest: Annotated[str, "Ticker that we are interested in trading"] # e.g BTC/USDT trade_date: Annotated[str, "What date we are trading at"] diff --git a/tradingagents/graph/propagation.py b/tradingagents/graph/propagation.py index bee1d8fb..c5d119dc 100644 --- a/tradingagents/graph/propagation.py +++ b/tradingagents/graph/propagation.py @@ -21,7 +21,6 @@ class Propagator: """Create the initial state for the agent graph.""" return { "messages": [("human", ticker)], - "company_of_interest": ticker, "ticker_of_interest": ticker, "trade_date": str(trade_date), "investment_debate_state": InvestDebateState( diff --git a/tradingagents/graph/trading_graph.py b/tradingagents/graph/trading_graph.py index 27b5e8f5..1307423e 100644 --- a/tradingagents/graph/trading_graph.py +++ b/tradingagents/graph/trading_graph.py @@ -206,7 +206,7 @@ class TradingAgentsGraph: def _log_state(self, trade_date, final_state): """Log the final state to a JSON file.""" self.log_states_dict[str(trade_date)] = { - "company_of_interest": final_state["company_of_interest"], + "ticker_of_interest": final_state["ticker_of_interest"], "trade_date": final_state["trade_date"], "market_report": final_state["market_report"], "sentiment_report": final_state["sentiment_report"], From cb9c63972a94dc92fafad77f34cbf0ae5dfe92d1 Mon Sep 17 00:00:00 2001 From: mhmmdjafarg Date: Mon, 29 Dec 2025 20:37:54 +0700 Subject: [PATCH 4/7] clean up config & singleton client --- .env.example | 1 + tradingagents/dataflows/binance.py | 34 ++++++++++++++----- .../dataflows/coin_gecko_fundamentals.py | 8 +++-- tradingagents/dataflows/interface.py | 3 -- tradingagents/dataflows/openai.py | 29 ++++++++++++---- tradingagents/dataflows/taapi.py | 23 +++++-------- tradingagents/dataflows/telegram.py | 18 ++++++---- tradingagents/default_config.py | 14 +++++--- 8 files changed, 85 insertions(+), 45 deletions(-) diff --git a/.env.example b/.env.example index 3c6f6dd4..6f5a8964 100644 --- a/.env.example +++ b/.env.example @@ -8,3 +8,4 @@ TAAPI_API_KEY=taapi_api_key_placeholder BYBIT_BASE_URL=https://api-demo.bybit.com BYBIT_API_KEY=bybit_api_key_placeholder BYBIT_API_SECRET=bybit_api_secret_placeholder +COIN_GECKO_API_BASE_URL=https://api.coingecko.com/api/v3 diff --git a/tradingagents/dataflows/binance.py b/tradingagents/dataflows/binance.py index f6c6569d..bdf4c160 100644 --- a/tradingagents/dataflows/binance.py +++ b/tradingagents/dataflows/binance.py @@ -1,20 +1,33 @@ from binance_common.configuration import ConfigurationRestAPI from binance_common.constants import SPOT_REST_API_PROD_URL from binance_sdk_spot.spot import Spot -import os from datetime import datetime import csv import io +from tradingagents.dataflows.config import get_config -def get_api_key() -> str: - """Retrieve the API key for Binance from environment variables.""" - api_key = os.getenv("BINANCE_API_KEY") - if not api_key: - raise ValueError("BINANCE_API_KEY environment variable is not set.") - return api_key +_client = None -configuration = ConfigurationRestAPI(api_key=get_api_key(), base_path=SPOT_REST_API_PROD_URL) -client = Spot(config_rest_api=configuration) +def get_binance_client(): + """Get or create Binance client with lazy initialization.""" + global _client + if _client is None: + try: + config = get_config() + api_key = config["external"].get("BINANCE_API_KEY", "") + if not api_key: + raise ValueError("BINANCE_API_KEY not found in configuration") + + configuration = ConfigurationRestAPI( + api_key=api_key, + base_path=SPOT_REST_API_PROD_URL + ) + _client = Spot(config_rest_api=configuration) + except Exception as e: + print(f"ERROR: Failed to initialize Binance client: {e}") + raise + + return _client def get_market_data(symbol: str, start_date: str, end_date: str): """Fetch market data for a given symbol from Binance. Get OHLCV data. interval is 1 day. @@ -36,6 +49,7 @@ def get_market_data(symbol: str, start_date: str, end_date: str): print(f"DEBUG: Fetching data for {formatted_symbol} from {start_date} to {end_date}") try: + client = get_binance_client() response = client.rest_api.klines( symbol=formatted_symbol, start_time=start_epoch, @@ -80,6 +94,8 @@ def get_market_data(symbol: str, start_date: str, end_date: str): csv_string = output.getvalue() output.close() + title = f"# Market Data for {symbol} from {start_date} to {end_date}\n\n" + csv_string = title + csv_string return csv_string except Exception as e: diff --git a/tradingagents/dataflows/coin_gecko_fundamentals.py b/tradingagents/dataflows/coin_gecko_fundamentals.py index 7e8f5759..15367a69 100644 --- a/tradingagents/dataflows/coin_gecko_fundamentals.py +++ b/tradingagents/dataflows/coin_gecko_fundamentals.py @@ -1,5 +1,6 @@ import requests from .alpha_vantage_common import API_BASE_URL +from tradingagents.dataflows.config import get_config def get_market_cap() -> str: """ @@ -8,8 +9,11 @@ def get_market_cap() -> str: Returns: str: Market capitalization percentage data for cryptocurrencies """ - endpoint = f"https://api.coingecko.com/api/v3/global" + config = get_config() + api_base_url = config["external"].get("COIN_GECKO_API_BASE_URL", "https://api.coingecko.com/api/v3") + endpoint = f"{api_base_url}/global" response = requests.get(endpoint) + print(f"DEBUG: CoinGecko API response status code: {response.status_code}") response.raise_for_status() data = response.json() market_cap_pct = data.get("data", {}).get("market_cap_percentage", {}) @@ -18,4 +22,4 @@ def get_market_cap() -> str: for coin, percentage in market_cap_pct.items(): # Format each line as "Coin: XX.XX%" result += f"- {coin.upper()}: {percentage:.2f}%\n" - return result \ No newline at end of file + return result diff --git a/tradingagents/dataflows/interface.py b/tradingagents/dataflows/interface.py index d79096d0..2c330da5 100644 --- a/tradingagents/dataflows/interface.py +++ b/tradingagents/dataflows/interface.py @@ -94,9 +94,6 @@ VENDOR_METHODS = { # technical_indicators "get_indicators": { "taapi": get_crypto_stats_indicators_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, diff --git a/tradingagents/dataflows/openai.py b/tradingagents/dataflows/openai.py index abc387f0..f643aee6 100644 --- a/tradingagents/dataflows/openai.py +++ b/tradingagents/dataflows/openai.py @@ -1,10 +1,27 @@ from openai import OpenAI from .config import get_config +_client = None + +def get_openai_client(): + """Get or create OpenAI client with lazy initialization.""" + global _client + if _client is None: + try: + config = get_config() + base_url = config.get("backend_url") + if not base_url: + raise ValueError("backend_url not found in configuration") + _client = OpenAI(base_url=base_url) + except Exception as e: + print(f"ERROR: Failed to initialize OpenAI client: {e}") + raise + + return _client def get_stock_news_openai(query, start_date, end_date): config = get_config() - client = OpenAI(base_url=config["backend_url"]) + client = get_openai_client() response = client.responses.create( model=config["quick_think_llm"], @@ -39,7 +56,7 @@ def get_stock_news_openai(query, start_date, end_date): def get_crypto_news_openai(query, start_date, end_date): config = get_config() - client = OpenAI(base_url=config["backend_url"]) + client = get_openai_client() response = client.responses.create( model=config["quick_think_llm"], @@ -74,7 +91,7 @@ def get_crypto_news_openai(query, start_date, end_date): def get_global_news_openai(curr_date, look_back_days=7, limit=5): config = get_config() - client = OpenAI(base_url=config["backend_url"]) + client = get_openai_client() response = client.responses.create( model=config["quick_think_llm"], @@ -110,7 +127,7 @@ def get_global_news_openai(curr_date, look_back_days=7, limit=5): def get_fundamentals_openai(ticker, curr_date): config = get_config() - client = OpenAI(base_url=config["backend_url"]) + client = get_openai_client() response = client.responses.create( model=config["quick_think_llm"], @@ -146,7 +163,7 @@ def get_fundamentals_openai(ticker, curr_date): def get_whitepaper_openai(symbol): config = get_config() - client = OpenAI(base_url=config["backend_url"]) + client = get_openai_client() response = client.responses.create( model=config["quick_think_llm"], @@ -177,4 +194,4 @@ def get_whitepaper_openai(symbol): store=True, ) - return response.output[1].content[0].text \ No newline at end of file + return response.output[1].content[0].text diff --git a/tradingagents/dataflows/taapi.py b/tradingagents/dataflows/taapi.py index c0d13777..456d28ec 100644 --- a/tradingagents/dataflows/taapi.py +++ b/tradingagents/dataflows/taapi.py @@ -1,16 +1,7 @@ import requests -from typing import Annotated, List, Dict -import os +from typing import Annotated, List from tradingagents.dataflows.config import get_config - -def get_api_key() -> str: - """Retrieve the API key for TAAPI from environment variables.""" - api_key = os.getenv("TAAPI_API_KEY") - if not api_key: - raise ValueError("TAAPI_API_KEY environment variable is not set.") - return api_key - # This is for single indicator, unused for now but kept for reference def get_crypto_stats_indicators_window( symbol: Annotated[str, "ticker symbol of the coin/asset"], @@ -58,8 +49,10 @@ def get_crypto_stats_indicators_window( return f"Error: Indicator '{indicator}' is not supported. Please choose from: {list(supported_indicators.keys())}" config = get_config() - base_url = config["tool_providers"].get("TAAPI_BASE_URL", "https://api.taapi.io") - api_key = get_api_key() + base_url = config["external"].get("TAAPI_BASE_URL", "https://api.taapi.io") + api_key = config["external"].get("TAAPI_API_KEY", "") + if not api_key: + return "Error: TAAPI_API_KEY is not set in the configuration." # Set backtrack as requested backtrack = look_back_days @@ -187,8 +180,10 @@ def get_crypto_stats_indicators( return f"Error: Indicators {invalid_indicators} are not supported. Please choose from: {list(supported_indicators.keys())}" config = get_config() - base_url = config["tool_providers"].get("TAAPI_BASE_URL", "https://api.taapi.io") - api_key = get_api_key() + base_url = config["external"].get("TAAPI_BASE_URL", "https://api.taapi.io") + api_key = config["external"].get("TAAPI_API_KEY", "") + if not api_key: + return "Error: TAAPI_API_KEY is not set in the configuration." # Construct the bulk API URL url = f"{base_url}/bulk" diff --git a/tradingagents/dataflows/telegram.py b/tradingagents/dataflows/telegram.py index 494d2c21..0d9b66ba 100644 --- a/tradingagents/dataflows/telegram.py +++ b/tradingagents/dataflows/telegram.py @@ -1,13 +1,19 @@ import asyncio from telethon import TelegramClient from datetime import datetime, timedelta, timezone -import os +from tradingagents.dataflows.config import get_config def get_api_credentials(): - api_id = int(os.getenv("TELEGRAM_API_ID", "")) - api_hash = os.getenv("TELEGRAM_API_HASH", "") - session_name = os.getenv("TELEGRAM_SESSION_NAME", "") - return api_id, api_hash, session_name + """Retrieve Telegram API credentials from environment variables.""" + config = get_config() + api_id = config["external"]["TELEGRAM_API_ID"] + api_hash = config["external"]["TELEGRAM_API_HASH"] + session_name = config["external"]["TELEGRAM_SESSION_NAME"] + + if not api_id or not api_hash or not session_name: + raise ValueError("Missing required Telegram credentials: TELEGRAM_API_ID, TELEGRAM_API_HASH, or TELEGRAM_SESSION_NAME") + + return int(api_id), api_hash, session_name async def _get_channel_history_async(start_date_str, end_date_str): """ @@ -55,4 +61,4 @@ def get_crypto_news_telegram(curr_date, look_back_days=7, limit=100): start_date_str = start_date.strftime('%Y-%m-%d') end_date_str = end_date.strftime('%Y-%m-%d') - return asyncio.run(_get_channel_history_async(start_date_str, end_date_str)) \ No newline at end of file + return asyncio.run(_get_channel_history_async(start_date_str, end_date_str)) diff --git a/tradingagents/default_config.py b/tradingagents/default_config.py index 419abb06..3709a302 100644 --- a/tradingagents/default_config.py +++ b/tradingagents/default_config.py @@ -21,23 +21,27 @@ DEFAULT_CONFIG = { # Category-level configuration (default for all tools in category) "data_vendors": { "core_crypto_apis": "binance", # Options: binance - "core_stock_apis": "yfinance", # Options: yfinance, alpha_vantage, local "technical_indicators": "taapi", # Options: taapi - "fundamental_data": "alpha_vantage", # Options: openai, alpha_vantage, local - "news_data": "openai", # Options: openai, alpha_vantage, google, local + "fundamental_data": "openai", # Options: openai + "news_data": "openai", # Options: local, openai, telegram }, # Tool-level configuration (takes precedence over category-level) "tool_vendors": { - # Example: "get_stock_data": "alpha_vantage", # Override category default "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"), + "TAAPI_API_KEY": os.getenv("TAAPI_API_KEY", ""), "BYBIT_BASE_URL": os.getenv("BYBIT_BASE_URL", "https://api-demo.bybit.com"), "BYBIT_API_KEY": os.getenv("BYBIT_API_KEY", ""), "BYBIT_API_SECRET": os.getenv("BYBIT_API_SECRET", ""), + "COIN_GECKO_API_BASE_URL": os.getenv("COIN_GECKO_API_BASE_URL", "https://api.coingecko.com/api/v3"), + "TELEGRAM_API_ID": os.getenv("TELEGRAM_API_ID", ""), + "TELEGRAM_API_HASH": os.getenv("TELEGRAM_API_HASH", ""), + "TELEGRAM_SESSION_NAME": os.getenv("TELEGRAM_SESSION_NAME", ""), } } From 9574a6dd6807ba2b32be7bdbadf0aaecfc9dc591 Mon Sep 17 00:00:00 2001 From: mhmmdjafarg Date: Mon, 29 Dec 2025 21:14:50 +0700 Subject: [PATCH 5/7] add fastapi webapp --- .env.example | 10 +++ README.md | 5 ++ pyproject.toml | 2 + requirements.txt | 2 + tradingagents/agents/utils/memory.py | 2 +- tradingagents/default_config.py | 15 ++-- webapp.py | 105 +++++++++++++++++++++++++++ 7 files changed, 133 insertions(+), 8 deletions(-) create mode 100644 webapp.py diff --git a/.env.example b/.env.example index 6f5a8964..100d87c1 100644 --- a/.env.example +++ b/.env.example @@ -9,3 +9,13 @@ BYBIT_BASE_URL=https://api-demo.bybit.com BYBIT_API_KEY=bybit_api_key_placeholder BYBIT_API_SECRET=bybit_api_secret_placeholder COIN_GECKO_API_BASE_URL=https://api.coingecko.com/api/v3 + +# Model settings +LLM_PROVIDER=openai +BACKEND_URL=https://api.openai.com/v1 +DEEP_THINK_LLM=gpt-4o-mini +QUICK_THINK_LLM=gpt-4o-mini + +# App settings +APP_HOST=localhost +APP_PORT=8000 diff --git a/README.md b/README.md index 2c1f07d0..4173bf5e 100644 --- a/README.md +++ b/README.md @@ -239,3 +239,8 @@ Please reference our work if you find *TradingAgents* provides you with some hel url={https://arxiv.org/abs/2412.20138}, } ``` + +## How to Run +``` +python3 webapp.py +``` diff --git a/pyproject.toml b/pyproject.toml index 63af4721..664e7831 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,6 +28,8 @@ dependencies = [ "rich>=14.0.0", "setuptools>=80.9.0", "stockstats>=0.6.5", + "fastapi>=0.115.0", + "uvicorn[standard]>=0.32.0", "tqdm>=4.67.1", "tushare>=1.4.21", "typing-extensions>=4.14.0", diff --git a/requirements.txt b/requirements.txt index 48de6aa7..57be465a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -26,3 +26,5 @@ langchain_anthropic langchain-google-genai binance-sdk-spot telethon +fastapi +uvicorn[standard] diff --git a/tradingagents/agents/utils/memory.py b/tradingagents/agents/utils/memory.py index 69b8ab8c..f8e1da2e 100644 --- a/tradingagents/agents/utils/memory.py +++ b/tradingagents/agents/utils/memory.py @@ -11,7 +11,7 @@ class FinancialSituationMemory: self.embedding = "text-embedding-3-small" self.client = OpenAI(base_url=config["backend_url"]) self.chroma_client = chromadb.Client(Settings(allow_reset=True)) - self.situation_collection = self.chroma_client.create_collection(name=name) + self.situation_collection = self.chroma_client.get_or_create_collection(name=name) def get_embedding(self, text): """Get OpenAI embedding for a text""" diff --git a/tradingagents/default_config.py b/tradingagents/default_config.py index 3709a302..d9e891c6 100644 --- a/tradingagents/default_config.py +++ b/tradingagents/default_config.py @@ -1,6 +1,10 @@ import os DEFAULT_CONFIG = { + # App config + "APP_HOST": "localhost", + "APP_PORT": 8000, + # Directory settings "project_dir": os.path.abspath(os.path.join(os.path.dirname(__file__), ".")), "results_dir": os.getenv("TRADINGAGENTS_RESULTS_DIR", "./results"), "data_dir": "/Users/yluo/Documents/Code/ScAI/FR1-data", @@ -9,10 +13,10 @@ DEFAULT_CONFIG = { "dataflows/data_cache", ), # LLM settings - "llm_provider": "openai", - "deep_think_llm": "o4-mini", - "quick_think_llm": "gpt-4o-mini", - "backend_url": "https://api.openai.com/v1", + "llm_provider": os.getenv("LLM_PROVIDER", "openai"), + "deep_think_llm": os.getenv("DEEP_THINK_LLM", "gpt-4o-mini"), + "quick_think_llm": os.getenv("QUICK_THINK_LLM", "gpt-4o-mini"), + "backend_url": os.getenv("BACKEND_URL","https://api.openai.com/v1"), # Debate and discussion settings "max_debate_rounds": 1, "max_risk_discuss_rounds": 1, @@ -29,9 +33,6 @@ DEFAULT_CONFIG = { "tool_vendors": { "get_global_news": "telegram" # Override category default }, - # Tool provider settings - "tool_providers": { - }, "external": { "BINANCE_API_KEY": os.getenv("BINANCE_API_KEY", ""), "TAAPI_BASE_URL": os.getenv("TAAPI_BASE_URL", "https://api.taapi.io"), diff --git a/webapp.py b/webapp.py new file mode 100644 index 00000000..a583fac7 --- /dev/null +++ b/webapp.py @@ -0,0 +1,105 @@ +from fastapi import FastAPI, HTTPException +from pydantic import BaseModel +import uvicorn +from datetime import datetime +import asyncio + +# Import your trading agents +from tradingagents.graph.trading_graph import TradingAgentsGraph +from tradingagents.dataflows.config import get_config +from dotenv import load_dotenv + +# Load environment variables +load_dotenv() + +config = get_config() + +# Create FastAPI app instance +app = FastAPI( + title="TradingAgents API", + description="API for TradingAgents financial trading framework", + version="0.1.0" +) + +# Pydantic models for request/response +class TradingRequest(BaseModel): + symbol: str + date: str + +class TradingResponse(BaseModel): + symbol: str + date: str + decision: dict + timestamp: str + status: str + +# Initialize trading agent once at startup +def create_trading_agent(): + """Create trading agent with fixed configuration""" + return TradingAgentsGraph(debug=True, config=config) + +# Create the trading agent instance once +trading_agent = create_trading_agent() + +@app.get("/") +async def root(): + """Root endpoint""" + return {"message": "Welcome to TradingAgents API"} + +@app.get("/ping") +async def ping(): + """Simple ping endpoint that returns pong""" + return {"message": "pong"} + +@app.get("/health") +async def health_check(): + """Health check endpoint""" + return { + "status": "healthy", + "timestamp": datetime.now().isoformat(), + "service": "tradingagents-api" + } + +@app.post("/trading/analyze", response_model=TradingResponse) +async def analyze_trading_decision(request: TradingRequest): + """ + Analyze trading decision for a given symbol and date + + Example usage: + POST /trading/analyze + { + "symbol": "NVDA", + "date": "2024-05-10" + } + """ + try: + # Run the analysis (this might take a while, so we run it in a thread pool) + def run_analysis(): + _, decision = trading_agent.propagate(request.symbol, request.date) + return decision + + # Run in thread pool to avoid blocking + loop = asyncio.get_event_loop() + decision = await loop.run_in_executor(None, run_analysis) + + return TradingResponse( + symbol=request.symbol, + date=request.date, + decision=decision, + timestamp=datetime.now().isoformat(), + status="success" + ) + + except Exception as e: + raise HTTPException(status_code=500, detail=f"Trading analysis failed: {str(e)}") + + +if __name__ == "__main__": + # Run the server + uvicorn.run( + "webapp:app", + host=config.get("APP_HOST", "localhost"), + port=config.get("APP_PORT", 8000), + reload=True, + log_level="info" + ) From 9b291d97ea90cf43bf7c78afefe1fa5beef4ef51 Mon Sep 17 00:00:00 2001 From: mhmmdjafarg Date: Tue, 30 Dec 2025 16:49:43 +0700 Subject: [PATCH 6/7] setup redis client --- .env.example | 5 +++++ README.md | 29 +++++++++++++---------------- docker-compose.yml | 18 ++++++++++++++++++ 3 files changed, 36 insertions(+), 16 deletions(-) create mode 100644 docker-compose.yml diff --git a/.env.example b/.env.example index 100d87c1..e5dfe265 100644 --- a/.env.example +++ b/.env.example @@ -19,3 +19,8 @@ QUICK_THINK_LLM=gpt-4o-mini # App settings APP_HOST=localhost APP_PORT=8000 + +# Redis +REDIS_HOST=redis +REDIS_PORT=6379 +REDIS_PASSWORD=password diff --git a/README.md b/README.md index 4173bf5e..56cde910 100644 --- a/README.md +++ b/README.md @@ -109,23 +109,25 @@ conda activate tradingagents pyenv pyenv local 3.12.7 python -m venv .venv -source./venv/bin/activate -python --version -pip install --upgrade pip -pip install -r requirements.txt -pip list - -# daily use source .venv/bin/activate +python --version +python -m pip install --upgrade pip +python -m pip install -r requirements.txt +python -m pip list + deactivate if error mini-racer -source venv/bin/activate && pip install --no-deps -r requirements.txt #install without miniracer +source .venv/bin/activate && pip install --no-deps -r requirements.txt #install without miniracer + +Api +python webapp.py ``` -Install dependencies: -```bash -pip install -r requirements.txt +### Connect to Redis (Local) +``` +docker-compose up -d +redis-cli -h localhost -p 6379 -a {REDIS_PASSWORD} ``` ### Required APIs @@ -239,8 +241,3 @@ Please reference our work if you find *TradingAgents* provides you with some hel url={https://arxiv.org/abs/2412.20138}, } ``` - -## How to Run -``` -python3 webapp.py -``` diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..14e2dbc7 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,18 @@ +services: + redis: + image: redis:7.4.7 + container_name: ${REDIS_CONTAINER_NAME:-trading_agents_redis} + restart: unless-stopped + ports: + - "6379:6379" + volumes: + - redis-data:/data + environment: + REDIS_PASSWORD: ${REDIS_PASSWORD} + command: > + redis-server + --appendonly yes + --requirepass ${REDIS_PASSWORD} + +volumes: + redis-data: From 4bee747f00b93f7fa4b2bf06722930b46f6d38ac Mon Sep 17 00:00:00 2001 From: mhmmdjafarg Date: Tue, 30 Dec 2025 17:40:22 +0700 Subject: [PATCH 7/7] add redis client --- .env.example | 5 ++-- requirements.txt | 1 + tradingagents/dataflows/bybit.py | 2 +- tradingagents/default_config.py | 10 ++++++-- tradingagents/external/redis/client.py | 34 ++++++++++++++++++++++++++ tradingagents/external/redis/repo.py | 18 ++++++++++++++ 6 files changed, 65 insertions(+), 5 deletions(-) create mode 100644 tradingagents/external/redis/client.py create mode 100644 tradingagents/external/redis/repo.py diff --git a/.env.example b/.env.example index e5dfe265..f5dce8a3 100644 --- a/.env.example +++ b/.env.example @@ -21,6 +21,7 @@ APP_HOST=localhost APP_PORT=8000 # Redis -REDIS_HOST=redis +REDIS_HOST=localhost REDIS_PORT=6379 -REDIS_PASSWORD=password +REDIS_PASSWORD=default-password +REDIS_DB=0 diff --git a/requirements.txt b/requirements.txt index 57be465a..d5ca8e83 100644 --- a/requirements.txt +++ b/requirements.txt @@ -28,3 +28,4 @@ binance-sdk-spot telethon fastapi uvicorn[standard] +redis[hiredis] \ No newline at end of file diff --git a/tradingagents/dataflows/bybit.py b/tradingagents/dataflows/bybit.py index d137001d..54997fdd 100644 --- a/tradingagents/dataflows/bybit.py +++ b/tradingagents/dataflows/bybit.py @@ -6,7 +6,7 @@ from typing import Dict, Optional from urllib.parse import urlencode import requests -from .config import get_config +from tradingagents.dataflows.config import get_config def bybit_v5_request(method: str, path: str, params: Optional[Dict] = None, body: Optional[Dict] = None) -> Dict: diff --git a/tradingagents/default_config.py b/tradingagents/default_config.py index d9e891c6..5151360d 100644 --- a/tradingagents/default_config.py +++ b/tradingagents/default_config.py @@ -44,5 +44,11 @@ DEFAULT_CONFIG = { "TELEGRAM_API_ID": os.getenv("TELEGRAM_API_ID", ""), "TELEGRAM_API_HASH": os.getenv("TELEGRAM_API_HASH", ""), "TELEGRAM_SESSION_NAME": os.getenv("TELEGRAM_SESSION_NAME", ""), - } -} + }, + "redis": { + "REDIS_HOST": os.getenv("REDIS_HOST", "localhost"), + "REDIS_PORT": int(os.getenv("REDIS_PORT", 6379)), + "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/external/redis/client.py b/tradingagents/external/redis/client.py new file mode 100644 index 00000000..2d9c6299 --- /dev/null +++ b/tradingagents/external/redis/client.py @@ -0,0 +1,34 @@ +from redis import Redis, ConnectionPool +from redis.backoff import ExponentialBackoff +from redis.retry import Retry +from tradingagents.dataflows.config import get_config + +_client = None + +def get_redis_client() -> Redis: + """Get or create Redis client with lazy initialization.""" + global _client + if _client is None: + try: + config = get_config() + retry = Retry(ExponentialBackoff(), retries=5) + + pool = ConnectionPool( + host=config["redis"]["REDIS_HOST"], + port=config["redis"]["REDIS_PORT"], + password=config["redis"]["REDIS_PASSWORD"], + db=config["redis"]["REDIS_DB"], + decode_responses=True, + socket_connect_timeout=5, + socket_timeout=5, + health_check_interval=10, + retry=retry, + ) + print("INFO: Initializing Redis client") + _client = Redis(connection_pool=pool) + print("INFO: Redis client initialized successfully") + except Exception as e: + print(f"ERROR: Failed to initialize Redis client: {e}") + raise + + return _client diff --git a/tradingagents/external/redis/repo.py b/tradingagents/external/redis/repo.py new file mode 100644 index 00000000..754919bd --- /dev/null +++ b/tradingagents/external/redis/repo.py @@ -0,0 +1,18 @@ +from tradingagents.external.redis.client import get_redis_client + +redis = get_redis_client() + +class RedisRepo: + def get(self, key: str): + return redis.get(key) + + def set(self, key: str, value: str, ex: int | None = None): + return redis.set(key, value, ex=ex) + + def delete(self, key: str): + return redis.delete(key) + + def exists(self, key: str) -> bool: + return redis.exists(key) == 1 + +redis_repo = RedisRepo()