feat: improve backtest with manual refresh and token address confirmation

This commit is contained in:
shokollm
2026-04-10 10:54:42 +00:00
parent 446da96ce4
commit f43eb11f6f
4 changed files with 101 additions and 47 deletions

View File

@@ -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
}

View File

@@ -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}")
# Get token address from strategy config (saved when user confirmed token)
token_address = None
token_symbol = None
# 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
# 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_info:
# Use first result if exact match not found
token_info = tokens[0]
if not token_address:
raise ValueError("Token address not found in strategy. Please update your strategy with a valid token.")
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
token_id = token_address
start_ts = None
end_ts = None

View File

@@ -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 {

View File

@@ -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 @@
</section>
<section class="results-section">
<h2>Backtest History</h2>
<div class="section-header">
<h2>Backtest History</h2>
<button class="btn-refresh" onclick={() => loadBacktests()} disabled={$backtestStore.isLoading}>
{$backtestStore.isLoading ? 'Refreshing...' : 'Refresh'}
</button>
</div>
{#if $backtestStore.backtestHistory.length === 0}
<p class="empty-state">No backtests yet. Run your first backtest above.</p>
@@ -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 {