add get acc balance and open orders tools for profile analyst

This commit is contained in:
rdyzakya 2025-12-30 14:36:50 +08:00
parent eeaab69396
commit f7bee7d1bb
7 changed files with 415 additions and 87 deletions

View File

@ -264,11 +264,277 @@
"open_orders = get_open_orders(symbol=\"BTCUSDT\", category=\"spot\")\n",
"print(\"Open orders:\", open_orders)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "330f7261",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 1,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from dotenv import load_dotenv\n",
"\n",
"load_dotenv()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "153dc726",
"metadata": {},
"outputs": [],
"source": [
"from tradingagents.dataflows.bybit import *"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "124d7e03",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"# Open Order Report for BTCUSDT\n",
"- Total Orders: 1\n",
"- Capital/Asset Value Locked: $0.00\n",
"\n",
"\n",
"## Conditional / Trigger Orders:\n",
"- (Buy): Trigger @ $0.00 | Qty: 0.01 — Created: 3.9d ago\n"
]
}
],
"source": [
"print(get_open_orders(\"BTC\", \"USDT\"))"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "d497b56a",
"metadata": {},
"outputs": [],
"source": [
"a = get_account_balance(\"BTC\", quote_coin=\"USDT\")"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "83f29137",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"# Account Balance Report for BTC/USDT\n",
"- Total Equity: $7919.6209963500005\n",
"## USDT (Quote) Details:\n",
"- Free Margin: 4722.44462368 USDT\n",
"- Locked Capital: 280.0 USDT\n",
"## BTC (Base) Details:\n",
"- Holding Size: 4e-07 BTC\n",
"- Locked Asset: 0.0 BTC\n",
"\n"
]
}
],
"source": [
"print(a)"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "6ca5af0f",
"metadata": {},
"outputs": [],
"source": [
"b = get_open_position(\"BTCUSDT\", category=\"linear\")"
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "b7ea9491",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'status': 'No Open Position',\n",
" 'symbol': 'BTCUSDT',\n",
" 'size': 0.0,\n",
" 'pnl_usd': 0.0}"
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"b"
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "6ead1651",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'nextPageCursor': '2113891849709292800%3A1766731545590%2C2113891849709292800%3A1766731545590',\n",
" 'category': 'spot',\n",
" 'list': [{'symbol': 'BTCUSDT',\n",
" 'orderType': 'Limit',\n",
" 'orderLinkId': '2113891849709292801',\n",
" 'slLimitPrice': '27500',\n",
" 'orderId': '2113891849709292800',\n",
" 'cancelType': 'UNKNOWN',\n",
" 'avgPrice': '0.0',\n",
" 'stopOrderType': '',\n",
" 'lastPriceOnCreated': '',\n",
" 'orderStatus': 'New',\n",
" 'takeProfit': '35000',\n",
" 'cumExecValue': '0.0000000',\n",
" 'smpType': 'None',\n",
" 'triggerDirection': 0,\n",
" 'blockTradeId': '',\n",
" 'cumFeeDetail': {},\n",
" 'isLeverage': '0',\n",
" 'rejectReason': 'EC_NoError',\n",
" 'price': '28000.0',\n",
" 'orderIv': '',\n",
" 'createdTime': '1766731545590',\n",
" 'tpTriggerBy': '',\n",
" 'positionIdx': 0,\n",
" 'trailingPercentage': '0',\n",
" 'timeInForce': 'PostOnly',\n",
" 'leavesValue': '280.0000000',\n",
" 'basePrice': '89223.5',\n",
" 'updatedTime': '1766731545591',\n",
" 'side': 'Buy',\n",
" 'smpGroup': 0,\n",
" 'triggerPrice': '0.0',\n",
" 'tpLimitPrice': '36000',\n",
" 'trailingValue': '0',\n",
" 'cumExecFee': '0',\n",
" 'leavesQty': '0.01',\n",
" 'slTriggerBy': '',\n",
" 'closeOnTrigger': False,\n",
" 'placeType': '',\n",
" 'cumExecQty': '0',\n",
" 'reduceOnly': False,\n",
" 'activationPrice': '0',\n",
" 'qty': '0.010000',\n",
" 'stopLoss': '27000',\n",
" 'marketUnit': '',\n",
" 'smpOrderId': '',\n",
" 'triggerBy': ''}]}"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"get_open_orders(symbol=\"BTCUSDT\", category=\"spot\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "50f72edb",
"metadata": {},
"outputs": [],
"source": [
"def get_symbol(base_coin: str, quote_coin: str, category: str = \"linear\") -> str:\n",
" \"\"\"\n",
" Safely retrieves the correct Bybit symbol (e.g., \"BTCUSDT\") for a given base/quote pair.\n",
" \n",
" Args:\n",
" base_coin: The asset (e.g., \"BTC\")\n",
" quote_coin: The currency (e.g., \"USDT\")\n",
" category: \"linear\", \"spot\", or \"inverse\"\n",
" \n",
" Returns:\n",
" The valid symbol string (e.g., \"BTCUSDT\") or None if not found.\n",
" \"\"\"\n",
" # 1. Query the API specifically for this Base Coin\n",
" # This filters the search on the server side, which is much faster.\n",
" params = {\n",
" \"category\": category,\n",
" \"baseCoin\": base_coin.upper(),\n",
" \"limit\": 20 # We only expect a few matches (e.g., BTCUSDT, BTC-PERP)\n",
" }\n",
" \n",
" data = bybit_v5_request(\"GET\", \"/v5/market/instruments-info\", params)\n",
"\n",
" result = data.get(\"result\", {})\n",
" instruments = result.get(\"list\", [])\n",
"\n",
" # 2. Find the exact match for the Quote Coin\n",
" # This handles cases where BTC might pair with USDT, USDC, or DAI\n",
" for item in instruments:\n",
" if item.get(\"quoteCoin\") == quote_coin.upper() and item.get(\"baseCoin\") == base_coin.upper():\n",
" return item.get(\"symbol\")\n",
"\n",
" # 3. Fallback/Error handling\n",
" return None"
]
},
{
"cell_type": "code",
"execution_count": 37,
"id": "6aa1fd5e",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'ETHBTC'"
]
},
"execution_count": 37,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"get_symbol(\"ETH\", \"BTC\", category=\"spot\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "61815a34",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": ".venv",
"display_name": "tradingagents",
"language": "python",
"name": "python3"
},
@ -282,7 +548,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.7"
"version": "3.13.11"
}
},
"nbformat": 4,

View File

@ -1,7 +1,7 @@
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.agents.utils.agent_utils import get_account_balance, get_open_orders
from tradingagents.dataflows.config import get_config
@ -11,19 +11,17 @@ def create_profile_analyst(llm):
ticker = state["ticker_of_interest"]
tools = [
# get_news,
# get_global_news,
get_account_balance,
get_open_orders,
]
system_message = """You are a Profile and Portfolio Analyst tasked with providing a deep-dive assessment of the user's personal trading account and financial health. Your role is to bridge the gap between market opportunities and the user's actual capacity to trade. Your objective is to write a comprehensive report detailing the user's buying power, asset allocation, risk exposure, and active market participation.
Use the available tools:
- `get_account_balance`: To determine total equity, available free margin for new trades, and locked capital.
- `get_portfolio_holdings`: To analyze current asset distribution, sector concentration (e.g., heavy on DeFi vs. Layer 1s), and unrealized PnL.
- `get_open_orders`: To identify capital tied up in pending limit orders or stop-losses that may need adjustment.
- `get_trade_history`: To review recent trading performance and identify behavioral patterns (e.g., panic selling or FOMO buying).
Do not simply list the user's balances or holdings. You must provide detailed and finegrained analysis and insights. For instance, warn the user if they are overexposed to a single volatile asset, point out if they have too many "stale" open orders locking up funds that could be used elsewhere, or analyze if their current cash position allows for aggressive or conservative moves based on the market conditions. Your report should serve as a risk management check before any new trades are executed. Make sure to append a Markdown table at the end of the report to organize key portfolio metrics (Total Equity, Free Margin, Top Holdings, Risk Level) and actionable recommendations, organized and easy to read."""
system_message = (
"You are a Profile and Portfolio Analyst tasked with providing a deep-dive assessment of the user's personal trading account and financial health. \
You will be given access to the user's portfolio data, your objective is to write a comprehensive long report detailing your analysis, insights, and implications for the user's trading capacity after assessing their buying power, asset allocation, risk exposure, and active market participation. \
Use the `get_account_balance(base_coin, quote_coin)` tool (e.g., base_coin='BTC', quote_coin='USDT') to determine total equity, free margin, and locked capital. Use the `get_open_orders(base_coin, quote_coin)` tool (e.g., base_coin='BTC', quote_coin='USDT') to identify capital tied up in pending limit orders or stop-losses. \
Do not simply list the user's balances or holdings, provide detailed and finegrained analysis and insights. For instance, warn the user if they are overexposed to a single volatile asset, point out if they have too many 'stale' open orders locking up funds, or analyze if their current cash position allows for aggressive moves. Your report should serve as a risk management check before any new trades are executed."
+ """ Make sure to append a Markdown table at the end of the report to organize key portfolio metrics (Total Equity, Free Margin, Top Holdings, Risk Level) and actionable recommendations, organized and easy to read.""",
)
prompt = ChatPromptTemplate.from_messages(
[

View File

@ -28,6 +28,10 @@ from tradingagents.agents.utils.news_data_tools import (
from tradingagents.agents.utils.sentiment_tools import (
get_fear_and_greed,
)
from tradingagents.agents.utils.profile_tools import (
get_account_balance,
get_open_orders,
)
def create_msg_delete():
def delete_messages(state):

View File

@ -4,58 +4,30 @@ from tradingagents.dataflows.interface import route_to_vendor
@tool
def get_account_balance(
curr_date: Annotated[str, "Current date in yyyy-mm-dd format"],
base_coin: Annotated[str, "The base coin symbol, e.g., 'USDT'"],
quote_coin: Annotated[str, "The quote coin symbol, e.g., 'BTC'"],
) -> str:
"""
Retrieve the user's account balance information.
Uses the configured profile_data vendor.
Fetches the account balance for a specific trading pair.
Args:
curr_date (str): Current date in yyyy-mm-dd format
base_coin (str): The base coin symbol, e.g., 'USDT'
quote_coin (str): The quote coin symbol, e.g., 'BTC'
Returns:
str: A report of the user's account balance
str: A formatted string containing account balance details
"""
return route_to_vendor("get_account_balance", curr_date)
@tool
def get_portfolio_holdings(
curr_date: Annotated[str, "Current date in yyyy-mm-dd format"],
) -> str:
"""
Retrieve the user's portfolio holdings information.
Uses the configured profile_data vendor.
Args:
curr_date (str): Current date in yyyy-mm-dd format
Returns:
str: A report of the user's portfolio holdings
"""
return route_to_vendor("get_portfolio_holdings", curr_date)
return route_to_vendor("get_account_balance", base_coin, quote_coin)
@tool
def get_open_orders(
curr_date: Annotated[str, "Current date in yyyy-mm-dd format"],
base_coin: Annotated[str, "The base coin symbol, e.g., 'USDT'"],
quote_coin: Annotated[str, "The quote coin symbol, e.g., 'BTC'"],
) -> str:
"""
Retrieve the user's open orders information.
Uses the configured profile_data vendor.
Fetches the list of open orders for a specific trading pair.
Args:
curr_date (str): Current date in yyyy-mm-dd format
base_coin (str): The base coin symbol, e.g., 'USDT'
quote_coin (str): The quote coin symbol, e.g., 'BTC'
Returns:
str: A report of the user's open orders
str: A formatted string containing open orders details
"""
return route_to_vendor("get_open_orders", curr_date)
@tool
def get_trade_history(
curr_date: Annotated[str, "Current date in yyyy-mm-dd format"],
look_back_days: Annotated[int, "Number of days to look back"] = 30,
) -> str:
"""
Retrieve the user's trade history information.
Uses the configured profile_data vendor.
Args:
curr_date (str): Current date in yyyy-mm-dd format
look_back_days (int): Number of days to look back (default 30)
Returns:
str: A report of the user's trade history
"""
return route_to_vendor("get_trade_history", curr_date, look_back_days)
return route_to_vendor("get_open_orders", base_coin, quote_coin)

View File

@ -8,6 +8,8 @@ from urllib.parse import urlencode
import requests
from .config import get_config
import json
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."""
@ -62,28 +64,109 @@ def bybit_v5_request(method: str, path: str, params: Optional[Dict] = None, body
return data
def get_wallet_balance(coin: str, account_type: str = "UNIFIED") -> float:
"""Fetch wallet balance for a specific coin from Bybit."""
def get_account_balance(base_coin: str, quote_coin: str = "USDT") -> dict:
"""
To determine total equity, available free margin for new trades, and locked capital.
Args:
base_coin: The asset being analyzed (e.g., "BTC")
quote_coin: The currency used for buying (e.g., "USDT")
"""
# 1. Fetch all assets from Bybit (omitting 'coin' gets everything)
data = bybit_v5_request("GET", "/v5/account/wallet-balance", {
"accountType": account_type,
"coin": coin.upper()
"accountType": "UNIFIED"
})
# Parse response to get wallet balance
# 2. Parse the raw response
try:
raw_list = data["result"]["list"][0]["coin"]
# Convert list to a dictionary for easy lookup: {'BTC': {...}, 'USDT': {...}}
result = {
item["coin"]: {k: float(v) if v else 0.0 for k, v in item.items() if k != "coin"}
for item in raw_list
}
except (IndexError, KeyError, TypeError):
result = {"error": "Could not retrieve wallet balance"}
total_equity = sum(asset.get("usdValue", 0.0) for asset in result.values())
report = f"# Account Balance Report for {base_coin}/{quote_coin}\n"
report += f"** Total Equity: ${total_equity} **\n"
report += f"## {quote_coin} (Quote) Details:\n"
report += json.dumps(result.get(quote_coin, {}), indent=2) + "\n"
report += f"## {base_coin} (Base) Details:\n"
report += json.dumps(result.get(base_coin, {}), indent=2) + "\n"
return report
def get_symbol(base_coin: str, quote_coin: str) -> str:
"""
Safely retrieves the correct Bybit symbol (e.g., "BTCUSDT") for a given base/quote pair.
Args:
base_coin: The asset (e.g., "BTC")
quote_coin: The currency (e.g., "USDT")
category: "linear", "spot", or "inverse"
Returns:
The valid symbol string (e.g., "BTCUSDT") or None if not found.
"""
# 1. Query the API specifically for this Base Coin
# This filters the search on the server side, which is much faster.
params = {
"category": "spot",
"baseCoin": base_coin.upper(),
"limit": 20 # We only expect a few matches (e.g., BTCUSDT, BTC-PERP)
}
data = bybit_v5_request("GET", "/v5/market/instruments-info", params)
result = data.get("result", {})
accounts = result.get("list", [])
instruments = result.get("list", [])
if not accounts:
return 0.0
# 2. Find the exact match for the Quote Coin
# This handles cases where BTC might pair with USDT, USDC, or DAI
for item in instruments:
if item.get("quoteCoin") == quote_coin.upper() and item.get("baseCoin") == base_coin.upper():
return item.get("symbol")
coins = accounts[0].get("coin", [])
for coin_data in coins:
if coin_data.get("coin") == coin.upper():
return float(coin_data.get("walletBalance", 0))
# 3. Fallback/Error handling
return None
return 0.0
def get_open_orders(base_coin: str, quote_coin: str) -> str:
"""
Fetches active orders and returns a text report analyzing capital lock-up and order age.
"""
symbol = get_symbol(base_coin, quote_coin)
if not symbol:
return f"Error: No valid spot symbol found for {base_coin}/{quote_coin}"
# 1. Fetch Open Orders
data = bybit_v5_request("GET", "/v5/order/realtime", {
"category": "spot",
"symbol": symbol.upper(),
"openOnly": 0 # 0=Active orders (Pending)
})
result = data.get("result", {})
orders = result.get("list", [])
for i in range(len(orders)):
# try to change to float if can
for k, v in orders[i].items():
if k in ["orderLinkId", "orderId"]:
continue
try:
orders[i][k] = float(v)
except:
pass
# change createdTime and updatedTime to yyyy-mm-dd hh:mm:ss format (utc)
orders[i]["createdTime"] = time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(orders[i]["createdTime"]/1000))
orders[i]["updatedTime"] = time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(orders[i]["updatedTime"]/1000))
report = f"# Open Orders for {symbol.upper()}\n"
report += json.dumps(orders, indent=2)
return report
def get_order_status(order_id: str, category: str = "spot") -> Dict:
"""
@ -130,26 +213,26 @@ def cancel_order(order_id: str, symbol: str, category: str = "spot") -> Dict:
return data.get("result", {})
def get_open_orders(symbol: Optional[str] = None, category: str = "spot") -> Dict:
"""
Get all open orders.
# 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")
# 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()
}
# Returns:
# Dict containing list of open orders
# """
# params = {
# "category": category.lower()
# }
if symbol:
params["symbol"] = symbol.upper()
# if symbol:
# params["symbol"] = symbol.upper()
data = bybit_v5_request("GET", "/v5/order/realtime", params=params)
return data.get("result", {})
# data = bybit_v5_request("GET", "/v5/order/realtime", params=params)
# return data.get("result", {})
def get_order_history(

View File

@ -25,6 +25,7 @@ DEFAULT_CONFIG = {
"technical_indicators": "taapi", # Options: taapi
"fundamental_data": "alpha_vantage", # Options: openai, alpha_vantage, local
"news_data": "openai", # Options: openai, alpha_vantage, google, local
"profile_data": "bybit", # Options: bybit, local
},
# Tool-level configuration (takes precedence over category-level)
"tool_vendors": {

View File

@ -38,7 +38,9 @@ from tradingagents.agents.utils.agent_utils import (
get_insider_sentiment,
get_insider_transactions,
get_global_news,
get_fear_and_greed
get_fear_and_greed,
get_account_balance,
get_open_orders
)
from .conditional_logic import ConditionalLogic
@ -169,6 +171,8 @@ class TradingAgentsGraph:
"profile": ToolNode(
[
# Profile analysis tools can be added here
get_account_balance,
get_open_orders,
]
),
}