From eeaab693969f3576ba3c500ca659e17a346bd1bc Mon Sep 17 00:00:00 2001 From: mhmmdjafarg Date: Fri, 26 Dec 2025 13:49:10 +0700 Subject: [PATCH] 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", ""), } }