diff --git a/src/backend/app/services/ai_agent/conversational.py b/src/backend/app/services/ai_agent/conversational.py index da85bcb..206523a 100644 --- a/src/backend/app/services/ai_agent/conversational.py +++ b/src/backend/app/services/ai_agent/conversational.py @@ -24,7 +24,7 @@ Your response must be valid JSON with exactly this structure: "thinking": "Your internal reasoning and analysis (what you're thinking about)", "response": "Your actual response to the user (be concise and helpful)", "strategy_update": null or { - "conditions": [{"type": "price_drop" | "price_rise" | "volume_spike" | "price_level", "token": "TOKEN", "threshold": number, ...}], + "conditions": [{"type": "price_drop" | "price_rise" | "volume_spike" | "price_level", "token": "TOKEN_SYMBOL", "token_address": null, "threshold": number, ...}], "actions": [{"type": "buy" | "sell" | "hold", "amount_percent": number, ...}], "risk_management": {"stop_loss_percent": number, "take_profit_percent": number} } @@ -34,6 +34,7 @@ Guidelines: - "thinking" should be detailed reasoning about the user's request - "response" should be conversational and clear - "strategy_update" should be populated ONLY when the user provides specific trading parameters (percentages, tokens, conditions, etc.) +- IMPORTANT: When a token is mentioned, set "token_address": null and ask user to confirm the token address before saving. Your response should say something like: "I need to confirm the token address. Could you provide the contract address for [TOKEN]?" - If no strategy parameters are provided, set "strategy_update" to null - Be friendly, concise, and helpful in your response @@ -45,15 +46,27 @@ User: "What can this bot do?" "strategy_update": null } -Example 2 (with strategy update): -User: "I want to buy PEPE when it drops 10% and take profit at 50%" +Example 2 (token needs confirmation): +User: "I want to buy PEPE when it drops 10%" { - "thinking": "User wants to buy PEPE when it drops 10%, with 50% take profit. I should parse these into conditions and actions.", - "response": "Got it! I've configured your strategy to buy PEPE when it drops 10%, with a 50% take profit target.", + "thinking": "User wants to buy PEPE. I need the token contract address to proceed. I should ask for confirmation.", + "response": "I'd be happy to set up a buy order for PEPE! However, I need to confirm the token contract address. Could you provide the BSC contract address for PEPE? (It usually starts with 0x...)", "strategy_update": { - "conditions": [{"type": "price_drop", "token": "PEPE", "threshold": 10}], + "conditions": [{"type": "price_drop", "token": "PEPE", "token_address": null, "threshold": 10}], "actions": [{"type": "buy", "amount_percent": 100}], - "risk_management": {"take_profit_percent": 50} + "risk_management": null + } +} + +Example 3 (with token address provided by user): +User: "Buy 0x6982508145454Ce125dDE157d8d64a26D53f60a2 when it drops 10%" +{ + "thinking": "User provided a contract address, I can use it directly.", + "response": "Perfect! I've configured your strategy to buy the token when it drops 10%.", + "strategy_update": { + "conditions": [{"type": "price_drop", "token": "TOKEN", "token_address": "0x6982508145454Ce125dDE157d8d64a26D53f60a2", "threshold": 10}], + "actions": [{"type": "buy", "amount_percent": 100}], + "risk_management": null } }""" @@ -91,9 +104,6 @@ class ConversationalAgent: messages.append({"role": "user", "content": user_message}) # Make API call to extended thinking endpoint - # Use requests library directly for this endpoint since it's not OpenAI-compatible - import requests - resp = requests.post( self.thinking_endpoint, headers={ @@ -153,6 +163,27 @@ class ConversationalAgent: # Use the native thinking from API if available, otherwise use parsed thinking final_thinking = thinking or thinking_field + # Check if token_address is missing in strategy_update + strategy_needs_confirmation = False + if strategy_update: + for cond in strategy_update.get("conditions", []): + if not cond.get("token_address"): + strategy_needs_confirmation = True + break + + # Only update strategy if token_address is provided + if strategy_update and strategy_needs_confirmation: + # Don't auto-save - user needs to confirm token address + # Return response but with strategy_update as None + return { + "response": response_text, + "thinking": final_thinking, + "strategy_updated": False, + "strategy_needs_confirmation": True, + "strategy_data": strategy_update, + "success": True + } + # Update strategy in database if provided if strategy_update and self.bot_id: self._update_strategy(strategy_update) @@ -161,6 +192,7 @@ class ConversationalAgent: "response": response_text, "thinking": final_thinking, "strategy_updated": strategy_update is not None, + "strategy_needs_confirmation": False, "success": True } diff --git a/src/backend/app/services/backtest/engine.py b/src/backend/app/services/backtest/engine.py index 13d7529..4e399cf 100644 --- a/src/backend/app/services/backtest/engine.py +++ b/src/backend/app/services/backtest/engine.py @@ -40,37 +40,28 @@ class BacktestEngine: started_at = datetime.utcnow() try: - token = self.config.get("token", "") chain = self.config.get("chain", "bsc") timeframe = self.config.get("timeframe", "1h") start_date = self.config.get("start_date", "") end_date = self.config.get("end_date", "") - # Search for token to get proper token_id (contract address) - try: - tokens = await self.ave_client.get_tokens(query=token, chain=chain, limit=10) - if not tokens: - raise ValueError(f"Token '{token}' not found on {chain}") - - # Find matching token - token_info = None - for t in tokens: - if t.get("symbol", "").upper() == token.upper() or t.get("name", "").upper() == token.upper(): - token_info = t - break - - if not token_info: - # Use first result if exact match not found - token_info = tokens[0] - - token_id = token_info.get("id") or token_info.get("contract_address") - if not token_id: - raise ValueError(f"Could not find token ID for '{token}'") - - except Exception as e: - self.status = "failed" - self.results = {"error": f"Failed to find token: {str(e)}"} - return self.results + # Get token address from strategy config (saved when user confirmed token) + token_address = None + token_symbol = None + + # Try to get from conditions first + if self.conditions: + token_address = self.conditions[0].get("token_address") + token_symbol = self.conditions[0].get("token") + # Fallback to actions + if not token_address and self.actions: + token_address = self.actions[0].get("token_address") + token_symbol = self.actions[0].get("token") or token_symbol + + if not token_address: + raise ValueError("Token address not found in strategy. Please update your strategy with a valid token.") + + token_id = token_address start_ts = None end_ts = None diff --git a/src/frontend/src/lib/api/types.ts b/src/frontend/src/lib/api/types.ts index 8fd857d..7df69c9 100644 --- a/src/frontend/src/lib/api/types.ts +++ b/src/frontend/src/lib/api/types.ts @@ -26,6 +26,7 @@ export interface StrategyConfig { export interface Condition { type: 'price_drop' | 'price_rise' | 'volume_spike' | 'price_level'; token: string; + token_address?: string; chain?: string; threshold?: number; price?: number; @@ -37,6 +38,7 @@ export interface Action { type: 'buy' | 'sell' | 'hold'; amount_percent?: number; token?: string; + token_address?: string; } export interface RiskManagement { diff --git a/src/frontend/src/routes/bot/[id]/backtest/+page.svelte b/src/frontend/src/routes/bot/[id]/backtest/+page.svelte index 993057e..45a35c4 100644 --- a/src/frontend/src/routes/bot/[id]/backtest/+page.svelte +++ b/src/frontend/src/routes/bot/[id]/backtest/+page.svelte @@ -32,15 +32,6 @@ if ($isAuthenticated && botId) { await loadBot(); await loadBacktests(); - - // Poll for backtest updates every 2 seconds if any are running - const pollInterval = setInterval(async () => { - if ($backtestStore.backtestHistory.some(b => b.status === 'running')) { - await loadBacktests(); - } - }, 2000); - - return () => clearInterval(pollInterval); } }); @@ -162,7 +153,12 @@
-

Backtest History

+
+

Backtest History

+ +
{#if $backtestStore.backtestHistory.length === 0}

No backtests yet. Run your first backtest above.

@@ -265,7 +261,40 @@ h2 { font-size: 1.25rem; - margin: 0 0 1rem; + margin: 0; + } + + .section-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1rem; + } + + .section-header h2 { + margin: 0; + } + + .btn-refresh { + padding: 0.5rem 1rem; + background: rgba(255, 255, 255, 0.1); + color: white; + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 6px; + font-size: 0.85rem; + cursor: pointer; + width: auto; + } + + .btn-refresh:hover:not(:disabled) { + background: rgba(255, 255, 255, 0.15); + transform: none; + } + + .btn-refresh:disabled { + opacity: 0.5; + cursor: not-allowed; + } } .content {