feat: Add AVE Cloud Skills as conversational agent tools (#56) #58

Merged
shoko merged 4 commits from fix/issue-56 into main 2026-04-13 14:56:33 +02:00
4 changed files with 534 additions and 218 deletions
Showing only changes of commit 99dded8d16 - Show all commits

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "ave-cloud-skill"]
path = ave-cloud-skill
url = https://github.com/AveCloud/ave-cloud-skill.git

1
ave-cloud-skill Submodule

Submodule ave-cloud-skill added at 5eaef99e15

1
src/backend/app/ave Symbolic link
View File

@@ -0,0 +1 @@
../../ave-cloud-skill/scripts/ave

View File

@@ -84,19 +84,84 @@ TOOLS = [
"type": "function",
"function": {
"name": "search_tokens",
"description": "Search for trending tokens on BSC blockchain. Use this when user asks for token recommendations, trending tokens, or wants to discover new tokens to trade. ALWAYS uses BSC chain.",
"description": "Search for tokens by keyword on BSC blockchain. Use this when user asks to search for a specific token or find tokens by name/symbol.",
"parameters": {
"type": "object",
"properties": {
"keyword": {
"type": "string",
"description": "Token symbol or name to search for (e.g., 'PEPE', 'BTC')",
},
"limit": {
"type": "integer",
"description": "Number of tokens to return (default: 10)",
"default": 10
"default": 10,
},
},
"required": ["keyword"],
},
},
},
{
"type": "function",
"function": {
"name": "get_token",
"description": "Get detailed information about a specific token including price, market cap, and pairs. Use when user asks for token details or wants to find a specific token by contract address.",
"parameters": {
"type": "object",
"properties": {
"address": {
"type": "string",
"description": "Token contract address (e.g., '0x6982508145454Ce125dDE157d8d64a26D53f60a2')",
},
"chain": {
"type": "string",
"description": "Blockchain chain (default: bsc)",
"default": "bsc",
},
},
"required": ["address"],
},
},
},
{
"type": "function",
"function": {
"name": "get_price",
"description": "Get current price(s) for tokens. Use when user asks for token price or wants to compare prices of multiple tokens.",
"parameters": {
"type": "object",
"properties": {
"token_ids": {
"type": "string",
"description": "Comma-separated list of token IDs with chain suffix (e.g., 'PEPE-bsc,TRUMP-bsc')",
}
},
"required": []
}
}
"required": ["token_ids"],
},
},
},
{
"type": "function",
"function": {
"name": "get_risk",
"description": "Get risk analysis for a token contract. Use when user asks about token risk, honeypot analysis, or safety assessment before trading.",
"parameters": {
"type": "object",
"properties": {
"address": {
"type": "string",
"description": "Token contract address (e.g., '0x6982508145454Ce125dDE157d8d64a26D53f60a2')",
},
"chain": {
"type": "string",
"description": "Blockchain chain (default: bsc)",
"default": "bsc",
},
},
"required": ["address"],
},
},
},
{
"type": "function",
@@ -108,25 +173,25 @@ TOOLS = [
"properties": {
"token_address": {
"type": "string",
"description": "The BSC contract address of the token to backtest (required)"
"description": "The BSC contract address of the token to backtest (required)",
},
"timeframe": {
"type": "string",
"description": "Timeframe for klines: '1d' (1 day), '4h' (4 hours), '1h' (1 hour), '15m' (15 minutes)",
"default": "1d"
"default": "1d",
},
"start_date": {
"type": "string",
"description": "Start date for backtest in YYYY-MM-DD format (e.g., '2024-01-01')"
"description": "Start date for backtest in YYYY-MM-DD format (e.g., '2024-01-01')",
},
"end_date": {
"type": "string",
"description": "End date for backtest in YYYY-MM-DD format (e.g., '2024-12-01')"
}
"description": "End date for backtest in YYYY-MM-DD format (e.g., '2024-12-01')",
},
},
"required": ["token_address"],
},
},
"required": ["token_address"]
}
}
},
{
"type": "function",
@@ -139,28 +204,33 @@ TOOLS = [
"action": {
"type": "string",
"enum": ["start", "stop", "status", "results"],
"description": "Action to perform: 'start' (begin new simulation), 'stop' (stop running simulation), 'status' (check if simulation is running), 'results' (get results from current or latest simulation)"
"description": "Action to perform: 'start' (begin new simulation), 'stop' (stop running simulation), 'status' (check if simulation is running), 'results' (get results from current or latest simulation)",
},
"token_address": {
"type": "string",
"description": "Token contract address for simulation (required for 'start' action)"
"description": "Token contract address for simulation (required for 'start' action)",
},
"kline_interval": {
"type": "string",
"description": "Kline interval: '1m', '5m', '15m', '1h' (default: '1m')",
"default": "1m"
}
"default": "1m",
},
},
"required": ["action"],
},
},
},
"required": ["action"]
}
}
}
]
SYSTEM_PROMPT_WITH_TOOLS = SYSTEM_PROMPT + """
SYSTEM_PROMPT_WITH_TOOLS = (
SYSTEM_PROMPT
+ """
You have access to tools:
- search_tokens(chain, limit): Search for trending tokens on a blockchain. Use it when user asks for token recommendations or trending tokens.
- search_tokens(keyword, limit): Search for tokens by keyword. Use it when user asks to search for a token or find tokens by name/symbol.
- get_token(address, chain): Get detailed information about a specific token. Use when user asks for token details.
- get_price(token_ids): Get current price(s) for tokens. Use when user asks for token price.
- get_risk(address, chain): Get risk analysis for a token. Use when user asks about token safety or honeypot analysis.
- run_backtest(token_address, timeframe, start_date, end_date): Run a backtest on historical data. Returns performance metrics. Use when user asks to backtest or check historical performance.
- manage_simulation(action, token_address, kline_interval): Manage trading simulations. Actions: 'start' (begin new), 'stop' (stop running), 'status' (check if running), 'results' (get current/latest results).
@@ -171,6 +241,7 @@ When you want to use a tool, respond with:
"tool_call": {"name": "run_backtest", "arguments": {"token_address": "0x...", "timeframe": "1d", "start_date": "2024-01-01", "end_date": "2024-12-01"}}
}
"""
)
class ConversationalAgent:
@@ -182,7 +253,9 @@ class ConversationalAgent:
# Extended thinking endpoint
self.thinking_endpoint = "https://api.minimax.io/v1/text/chatcompletion_v2"
def chat(self, user_message: str, conversation_history: List[Dict] = None) -> Dict[str, Any]:
def chat(
self, user_message: str, conversation_history: List[Dict] = None
) -> Dict[str, Any]:
"""Process a user message and return a structured response.
Args:
@@ -210,19 +283,16 @@ class ConversationalAgent:
self.thinking_endpoint,
headers={
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
"Content-Type": "application/json",
},
json={
"model": self.model,
"messages": messages,
"temperature": 0.7,
"max_tokens": 2000,
"thinking": {
"type": "human",
"budget_tokens": 1500
"thinking": {"type": "human", "budget_tokens": 1500},
"tools": TOOLS,
},
"tools": TOOLS
}
)
result = resp.json()
@@ -244,41 +314,213 @@ class ConversationalAgent:
args = json.loads(func.get("arguments", "{}"))
if func_name == "search_tokens":
chain = "bsc" # Always BSC
keyword = args.get("keyword", "")
limit = args.get("limit", 10)
# Execute the tool
from ..ave.client import AveCloudClient
from ...core.config import get_settings
settings = get_settings()
ave_client = AveCloudClient(
api_key=settings.AVE_API_KEY,
plan=settings.AVE_API_PLAN
)
# Execute the tool using ave-cloud-skill
import os
import asyncio
tokens = asyncio.run(ave_client.get_tokens(chain=chain, limit=limit))
from ...core.config import get_settings
settings = get_settings()
os.environ["AVE_API_KEY"] = settings.AVE_API_KEY
os.environ["API_PLAN"] = settings.AVE_API_PLAN
from ave.http import api_get
resp = asyncio.run(
api_get(
"/tokens",
{
"keyword": keyword,
"limit": limit,
"chain": "bsc",
},
)
)
if resp.status_code == 200:
data = resp.json()
tokens = data.get("data", {}).get("tokens", [])
if tokens:
# Format tokens for response
token_list = ""
for t in tokens[:limit]:
addr = t.get("token", "")
symbol = t.get("symbol", "")
name = t.get("name", "")
price_change = t.get("token_price_change_24h", "N/A")
token_list += f"- **{symbol}** ({name}): `{addr}` - 24h change: {price_change}%\n"
response_text = f"Here are the trending tokens on {chain.upper()}:\n\n{token_list}\nWould you like me to set up a strategy for any of these?"
price_change = t.get(
"token_price_change_24h", "N/A"
)
mc = t.get("market_cap", "N/A")
token_list += f"- **{symbol}** ({name}): `{addr}` - MC: ${mc:,.0f} - 24h: {price_change}%\n"
response_text = f"Here are the search results for '{keyword}' on BSC:\n\n{token_list}\nWould you like me to set up a strategy for any of these?"
else:
response_text = f"I couldn't find any trending tokens on {chain.upper()}. Try again later."
response_text = f"No tokens found for '{keyword}'. Try a different keyword."
else:
response_text = (
f"Failed to search tokens: {resp.status_code}"
)
# Return the tool result directly
return {
"response": response_text,
"thinking": thinking,
"strategy_updated": False,
"strategy_needs_confirmation": False,
"success": True
"success": True,
}
elif func_name == "get_token":
address = args.get("address", "")
chain = args.get("chain", "bsc")
import os
import asyncio
from ...core.config import get_settings
settings = get_settings()
os.environ["AVE_API_KEY"] = settings.AVE_API_KEY
os.environ["API_PLAN"] = settings.AVE_API_PLAN
from ave.http import api_get
resp = asyncio.run(
api_get(f"/tokens/{address}-{chain}")
)
if resp.status_code == 200:
data = resp.json()
token_data = data.get("data", {})
if token_data:
symbol = token_data.get("symbol", "N/A")
name = token_data.get("name", "N/A")
price = token_data.get("price", "N/A")
mc = token_data.get("market_cap", "N/A")
vol = token_data.get("volume_24h", "N/A")
pairs = token_data.get("top_pairs", [])
pairs_text = ""
if pairs:
pairs_text = "\n**Top Pairs:**\n"
for p in pairs[:3]:
pairs_text += f"- {p.get('pair', 'N/A')}: ${p.get('liquidity', 'N/A'):,.0f} liquidity\n"
response_text = f"**{symbol}** ({name})\n\nPrice: ${price}\nMarket Cap: ${mc:,.0f}\n24h Volume: ${vol:,.0f}{pairs_text}"
else:
response_text = f"Token not found: {address}"
else:
response_text = f"Failed to get token details: {resp.status_code}"
return {
"response": response_text,
"thinking": thinking,
"strategy_updated": False,
"strategy_needs_confirmation": False,
"success": True,
}
elif func_name == "get_price":
token_ids = args.get("token_ids", "")
import os
import asyncio
from ...core.config import get_settings
settings = get_settings()
os.environ["AVE_API_KEY"] = settings.AVE_API_KEY
os.environ["API_PLAN"] = settings.AVE_API_PLAN
from ave.http import api_post
tokens_list = [t.strip() for t in token_ids.split(",")]
resp = asyncio.run(
api_post(
"/tokens/price", {"token_ids": tokens_list}
)
)
if resp.status_code == 200:
data = resp.json()
prices = data.get("data", {})
if prices:
price_text = "**Token Prices:**\n"
for token_id, price_data in prices.items():
price = price_data.get("price", "N/A")
change_24h = price_data.get(
"token_price_change_24h", "N/A"
)
price_text += f"- {token_id}: ${price} (24h: {change_24h}%)\n"
response_text = price_text
else:
response_text = "No price data available."
else:
response_text = (
f"Failed to get prices: {resp.status_code}"
)
return {
"response": response_text,
"thinking": thinking,
"strategy_updated": False,
"strategy_needs_confirmation": False,
"success": True,
}
elif func_name == "get_risk":
address = args.get("address", "")
chain = args.get("chain", "bsc")
import os
import asyncio
from ...core.config import get_settings
settings = get_settings()
os.environ["AVE_API_KEY"] = settings.AVE_API_KEY
os.environ["API_PLAN"] = settings.AVE_API_PLAN
from ave.http import api_get
resp = asyncio.run(
api_get(f"/contracts/{address}-{chain}")
)
if resp.status_code == 200:
data = resp.json()
risk_data = data.get("data", {})
if risk_data:
is_honeypot = risk_data.get(
"is_honeypot", "unknown"
)
buy_tax = risk_data.get("buy_tax", "N/A")
sell_tax = risk_data.get("sell_tax", "N/A")
status = risk_data.get("status", "unknown")
risk_text = (
f"**Risk Analysis for {address}**\n\n"
)
risk_text += f"- Status: {status}\n"
risk_text += f"- Honeypot: {is_honeypot}\n"
risk_text += f"- Buy Tax: {buy_tax}%\n"
risk_text += f"- Sell Tax: {sell_tax}%\n"
if is_honeypot.lower() == "true":
risk_text += "\n⚠️ **Warning: This token appears to be a honeypot. Do not buy!**"
elif (
float(buy_tax or 0) > 10
or float(sell_tax or 0) > 10
):
risk_text += "\n⚠️ **Warning: High tax detected. Trade with caution!**"
else:
risk_text += (
"\n✅ This token appears safe to trade."
)
response_text = risk_text
else:
response_text = (
f"No risk data available for {address}"
)
else:
response_text = (
f"Failed to get risk data: {resp.status_code}"
)
return {
"response": response_text,
"thinking": thinking,
"strategy_updated": False,
"strategy_needs_confirmation": False,
"success": True,
}
elif func_name == "run_backtest":
@@ -292,7 +534,7 @@ class ConversationalAgent:
token_address=token_address,
timeframe=timeframe,
start_date=start_date,
end_date=end_date
end_date=end_date,
)
return {
@@ -300,7 +542,7 @@ class ConversationalAgent:
"thinking": thinking,
"strategy_updated": False,
"strategy_needs_confirmation": False,
"success": True
"success": True,
}
elif func_name == "manage_simulation":
@@ -312,7 +554,7 @@ class ConversationalAgent:
sim_result = self._manage_simulation(
action=action,
token_address=token_address,
kline_interval=kline_interval
kline_interval=kline_interval,
)
return {
@@ -320,11 +562,13 @@ class ConversationalAgent:
"thinking": thinking,
"strategy_updated": False,
"strategy_needs_confirmation": False,
"success": True
"success": True,
}
# Get the main response content
content = result.get("choices", [{}])[0].get("message", {}).get("content", "")
content = (
result.get("choices", [{}])[0].get("message", {}).get("content", "")
)
# Parse JSON from the content
thinking_field = None
@@ -332,12 +576,14 @@ class ConversationalAgent:
strategy_update = None
# Try to extract JSON from the content
json_match = re.search(r'```(?:json)?\s*(\{.*?\})\s*```', content, re.DOTALL)
json_match = re.search(
r"```(?:json)?\s*(\{.*?\})\s*```", content, re.DOTALL
)
if json_match:
json_str = json_match.group(1)
else:
# Try to find JSON object directly
json_match = re.search(r'\{.*\}', content, re.DOTALL)
json_match = re.search(r"\{.*\}", content, re.DOTALL)
if json_match:
json_str = json_match.group(0)
else:
@@ -354,33 +600,47 @@ class ConversationalAgent:
tool_call = parsed.get("tool_call")
if tool_call and tool_call.get("name") == "search_tokens":
args = tool_call.get("arguments", {})
chain = args.get("chain", "bsc")
keyword = args.get("keyword", "")
limit = args.get("limit", 10)
# Execute the tool
from ..ave.client import AveCloudClient
from ...core.config import get_settings
settings = get_settings()
ave_client = AveCloudClient(
api_key=settings.AVE_API_KEY,
plan=settings.AVE_API_PLAN
)
# Execute the tool using ave-cloud-skill
import os
import asyncio
tokens = asyncio.run(ave_client.get_tokens(chain=chain, limit=limit))
from ...core.config import get_settings
settings = get_settings()
os.environ["AVE_API_KEY"] = settings.AVE_API_KEY
os.environ["API_PLAN"] = settings.AVE_API_PLAN
from ave.http import api_get
resp = asyncio.run(
api_get(
"/tokens",
{"keyword": keyword, "limit": limit, "chain": "bsc"},
)
)
if resp.status_code == 200:
data = resp.json()
tokens = data.get("data", {}).get("tokens", [])
if tokens:
# Format tokens for response
token_list = ""
for t in tokens[:limit]:
addr = t.get("token", "")
symbol = t.get("symbol", "")
name = t.get("name", "")
price_change = t.get("token_price_change_24h", "N/A")
token_list += f"- **{symbol}** ({name}): `{addr}` - 24h change: {price_change}%\n"
response_text = f"Here are the trending tokens on {chain.upper()}:\n\n{token_list}\nWould you like me to set up a strategy for any of these?"
price_change = t.get(
"token_price_change_24h", "N/A"
)
mc = t.get("market_cap", "N/A")
token_list += f"- **{symbol}** ({name}): `{addr}` - MC: ${mc:,.0f} - 24h: {price_change}%\n"
response_text = f"Here are the search results for '{keyword}' on BSC:\n\n{token_list}\nWould you like me to set up a strategy for any of these?"
else:
response_text = f"I couldn't find any trending tokens on {chain.upper()}. Try again later."
response_text = f"No tokens found for '{keyword}'. Try a different keyword."
else:
response_text = (
f"Failed to search tokens: {resp.status_code}"
)
strategy_update = None
@@ -408,21 +668,28 @@ class ConversationalAgent:
try:
from ..ave.client import AveCloudClient
from ...core.config import get_settings
settings = get_settings()
ave_client = AveCloudClient(
api_key=settings.AVE_API_KEY,
plan=settings.AVE_API_PLAN
api_key=settings.AVE_API_KEY, plan=settings.AVE_API_PLAN
)
# Run async search in sync context
import asyncio
tokens = asyncio.run(ave_client.get_tokens(query=token_name, chain="bsc", limit=5))
tokens = asyncio.run(
ave_client.get_tokens(
query=token_name, chain="bsc", limit=5
)
)
if tokens:
token_search_results = [
{
"symbol": t.get("symbol", ""),
"name": t.get("name", ""),
"address": t.get("token", ""), # trending API uses "token" for contract address
"chain": t.get("chain", "bsc")
"address": t.get(
"token", ""
), # trending API uses "token" for contract address
"chain": t.get("chain", "bsc"),
}
for t in tokens
]
@@ -440,7 +707,7 @@ class ConversationalAgent:
"strategy_needs_confirmation": True,
"strategy_data": strategy_update,
"token_search_results": token_search_results,
"success": True
"success": True,
}
# Update strategy in database if provided
@@ -452,7 +719,7 @@ class ConversationalAgent:
"thinking": final_thinking,
"strategy_updated": strategy_update is not None,
"strategy_needs_confirmation": False,
"success": True
"success": True,
}
except Exception as e:
@@ -460,7 +727,7 @@ class ConversationalAgent:
"response": f"I encountered an error: {str(e)}. Please try again.",
"thinking": None,
"strategy_updated": False,
"success": False
"success": False,
}
def _execute_backtest(
@@ -468,7 +735,7 @@ class ConversationalAgent:
token_address: str,
timeframe: str = "1d",
start_date: str = None,
end_date: str = None
end_date: str = None,
) -> str:
"""Execute a backtest using the bot's current strategy."""
try:
@@ -524,7 +791,9 @@ class ConversationalAgent:
# Format return with emoji indicators
return_emoji = "📈" if total_return >= 0 else "📉"
return_str = f"+{total_return:.2f}%" if total_return >= 0 else f"{total_return:.2f}%"
return_str = (
f"+{total_return:.2f}%" if total_return >= 0 else f"{total_return:.2f}%"
)
drawdown_emoji = "⚠️" if abs(max_drawdown) > 10 else ""
@@ -550,10 +819,7 @@ Would you like me to adjust the strategy parameters based on these results?"""
return f"I encountered an error running the backtest: {str(e)}"
def _manage_simulation(
self,
action: str,
token_address: str = None,
kline_interval: str = "1m"
self, action: str, token_address: str = None, kline_interval: str = "1m"
) -> str:
"""Manage trading simulations: start, stop, status, or results."""
try:
@@ -579,10 +845,14 @@ Would you like me to adjust the strategy parameters based on these results?"""
return "I need a token address to start a simulation. Which token would you like to simulate?"
# Check if there's already a running simulation
running_sim = db.query(Simulation).filter(
running_sim = (
db.query(Simulation)
.filter(
Simulation.bot_id == self.bot_id,
Simulation.status == "running"
).first()
Simulation.status == "running",
)
.first()
)
if running_sim:
# Stop the existing one first
@@ -598,11 +868,11 @@ Would you like me to adjust the strategy parameters based on these results?"""
config={
"token": token_address,
"chain": "bsc",
"kline_interval": kline_interval
"kline_interval": kline_interval,
},
signals=[],
klines=[],
trade_log=[]
trade_log=[],
)
db.add(simulation)
db.commit()
@@ -623,7 +893,11 @@ Would you like me to adjust the strategy parameters based on these results?"""
# Run simulation in background thread
def run_sim():
asyncio.run(self._run_simulation_sync(sim_id, settings.DATABASE_URL, sim_config))
asyncio.run(
self._run_simulation_sync(
sim_id, settings.DATABASE_URL, sim_config
)
)
thread = threading.Thread(target=run_sim)
thread.daemon = True
@@ -633,10 +907,14 @@ Would you like me to adjust the strategy parameters based on these results?"""
elif action == "stop":
# Find running simulation
running_sim = db.query(Simulation).filter(
running_sim = (
db.query(Simulation)
.filter(
Simulation.bot_id == self.bot_id,
Simulation.status == "running"
).first()
Simulation.status == "running",
)
.first()
)
if not running_sim:
return "No simulation is currently running."
@@ -648,16 +926,22 @@ Would you like me to adjust the strategy parameters based on these results?"""
current_balance = portfolio.get("current_balance", 10000)
initial_balance = portfolio.get("initial_balance", 10000)
pnl = current_balance - initial_balance
pnl_pct = (pnl / initial_balance) * 100 if initial_balance > 0 else 0
pnl_pct = (
(pnl / initial_balance) * 100 if initial_balance > 0 else 0
)
return f"Simulation stopped!\n\nFinal Results:\n💰 Final Balance: ${current_balance:,.2f}\n📈 P&L: {'+' if pnl >= 0 else ''}${pnl:,.2f} ({'+' if pnl_pct >= 0 else ''}{pnl_pct:.2f}%)\n📊 Trades: {len(running_sim.trade_log or [])}"
elif action == "status":
# Find running simulation
running_sim = db.query(Simulation).filter(
running_sim = (
db.query(Simulation)
.filter(
Simulation.bot_id == self.bot_id,
Simulation.status == "running"
).first()
Simulation.status == "running",
)
.first()
)
if not running_sim:
return "No simulation is currently running."
@@ -672,7 +956,9 @@ Would you like me to adjust the strategy parameters based on these results?"""
if portfolio.get("position", 0) > 0:
status += f"💰 Position: {portfolio['position']:.4f} {portfolio.get('position_token', 'TOKEN')}\n"
status += f"💰 Cash: ${portfolio.get('current_balance', 0):,.2f}\n"
status += (
f"💰 Cash: ${portfolio.get('current_balance', 0):,.2f}\n"
)
else:
status += f"💰 Cash: ${portfolio.get('current_balance', 10000):,.2f}\n"
@@ -681,9 +967,12 @@ Would you like me to adjust the strategy parameters based on these results?"""
elif action == "results":
# Find running or most recent simulation
simulation = db.query(Simulation).filter(
Simulation.bot_id == self.bot_id
).order_by(Simulation.started_at.desc()).first()
simulation = (
db.query(Simulation)
.filter(Simulation.bot_id == self.bot_id)
.order_by(Simulation.started_at.desc())
.first()
)
if not simulation:
return "No simulation found. Start a simulation first!"
@@ -692,19 +981,29 @@ Would you like me to adjust the strategy parameters based on these results?"""
current_balance = portfolio.get("current_balance", 10000)
initial_balance = portfolio.get("initial_balance", 10000)
pnl = current_balance - initial_balance
pnl_pct = (pnl / initial_balance) * 100 if initial_balance > 0 else 0
pnl_pct = (
(pnl / initial_balance) * 100 if initial_balance > 0 else 0
)
trade_log = simulation.trade_log or []
status_emoji = "🟢" if simulation.status == "running" else ""
status_text = "Running" if simulation.status == "running" else "Completed/Stopped"
status_text = (
"Running"
if simulation.status == "running"
else "Completed/Stopped"
)
results = f"**Simulation Results** {status_emoji} ({status_text})\n\n"
results = (
f"**Simulation Results** {status_emoji} ({status_text})\n\n"
)
results += f"💰 Final Balance: ${current_balance:,.2f}\n"
results += f"📈 P&L: {'+' if pnl >= 0 else ''}${pnl:,.2f} ({'+' if pnl_pct >= 0 else ''}{pnl_pct:.2f}%)\n"
results += f"📊 Total Trades: {len(trade_log)}\n"
if simulation.status == "running":
results += f"\n⏳ Simulation still running... (refresh for latest)"
results += (
f"\n⏳ Simulation still running... (refresh for latest)"
)
return results
@@ -720,9 +1019,12 @@ Would you like me to adjust the strategy parameters based on these results?"""
def _stop_simulation_db(self, simulation_id: str):
"""Stop a simulation in the database."""
from ...core.database import SessionLocal
db = SessionLocal()
try:
simulation = db.query(Simulation).filter(Simulation.id == simulation_id).first()
simulation = (
db.query(Simulation).filter(Simulation.id == simulation_id).first()
)
if simulation:
simulation.status = "stopped"
db.commit()
@@ -747,11 +1049,18 @@ Would you like me to adjust the strategy parameters based on these results?"""
def save_progress():
db = SessionLocal()
try:
sim = db.query(Simulation).filter(Simulation.id == simulation_id).first()
sim = (
db.query(Simulation)
.filter(Simulation.id == simulation_id)
.first()
)
if sim:
sim.status = engine.status
sim.signals = [serialize_signal(s) for s in engine.signals]
sim.klines = [{"time": k.get("time"), "close": k.get("close")} for k in engine.klines]
sim.klines = [
{"time": k.get("time"), "close": k.get("close")}
for k in engine.klines
]
sim.trade_log = engine.trade_log
sim.portfolio = {
"initial_balance": config.get("initial_balance", 10000),
@@ -788,7 +1097,9 @@ Would you like me to adjust the strategy parameters based on these results?"""
return False
def get_conversational_agent(api_key: str = None, model: str = None, bot_id: str = None) -> ConversationalAgent:
def get_conversational_agent(
api_key: str = None, model: str = None, bot_id: str = None
) -> ConversationalAgent:
"""Get or create a ConversationalAgent instance."""
if api_key is None:
settings = get_settings()