feat: improve backtest with manual refresh and token address confirmation
This commit is contained in:
@@ -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)",
|
"thinking": "Your internal reasoning and analysis (what you're thinking about)",
|
||||||
"response": "Your actual response to the user (be concise and helpful)",
|
"response": "Your actual response to the user (be concise and helpful)",
|
||||||
"strategy_update": null or {
|
"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, ...}],
|
"actions": [{"type": "buy" | "sell" | "hold", "amount_percent": number, ...}],
|
||||||
"risk_management": {"stop_loss_percent": number, "take_profit_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
|
- "thinking" should be detailed reasoning about the user's request
|
||||||
- "response" should be conversational and clear
|
- "response" should be conversational and clear
|
||||||
- "strategy_update" should be populated ONLY when the user provides specific trading parameters (percentages, tokens, conditions, etc.)
|
- "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
|
- If no strategy parameters are provided, set "strategy_update" to null
|
||||||
- Be friendly, concise, and helpful in your response
|
- Be friendly, concise, and helpful in your response
|
||||||
|
|
||||||
@@ -45,15 +46,27 @@ User: "What can this bot do?"
|
|||||||
"strategy_update": null
|
"strategy_update": null
|
||||||
}
|
}
|
||||||
|
|
||||||
Example 2 (with strategy update):
|
Example 2 (token needs confirmation):
|
||||||
User: "I want to buy PEPE when it drops 10% and take profit at 50%"
|
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.",
|
"thinking": "User wants to buy PEPE. I need the token contract address to proceed. I should ask for confirmation.",
|
||||||
"response": "Got it! I've configured your strategy to buy PEPE when it drops 10%, with a 50% take profit target.",
|
"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": {
|
"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}],
|
"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})
|
messages.append({"role": "user", "content": user_message})
|
||||||
|
|
||||||
# Make API call to extended thinking endpoint
|
# 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(
|
resp = requests.post(
|
||||||
self.thinking_endpoint,
|
self.thinking_endpoint,
|
||||||
headers={
|
headers={
|
||||||
@@ -153,6 +163,27 @@ class ConversationalAgent:
|
|||||||
# Use the native thinking from API if available, otherwise use parsed thinking
|
# Use the native thinking from API if available, otherwise use parsed thinking
|
||||||
final_thinking = thinking or thinking_field
|
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
|
# Update strategy in database if provided
|
||||||
if strategy_update and self.bot_id:
|
if strategy_update and self.bot_id:
|
||||||
self._update_strategy(strategy_update)
|
self._update_strategy(strategy_update)
|
||||||
@@ -161,6 +192,7 @@ class ConversationalAgent:
|
|||||||
"response": response_text,
|
"response": response_text,
|
||||||
"thinking": final_thinking,
|
"thinking": final_thinking,
|
||||||
"strategy_updated": strategy_update is not None,
|
"strategy_updated": strategy_update is not None,
|
||||||
|
"strategy_needs_confirmation": False,
|
||||||
"success": True
|
"success": True
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,37 +40,28 @@ class BacktestEngine:
|
|||||||
started_at = datetime.utcnow()
|
started_at = datetime.utcnow()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
token = self.config.get("token", "")
|
|
||||||
chain = self.config.get("chain", "bsc")
|
chain = self.config.get("chain", "bsc")
|
||||||
timeframe = self.config.get("timeframe", "1h")
|
timeframe = self.config.get("timeframe", "1h")
|
||||||
start_date = self.config.get("start_date", "")
|
start_date = self.config.get("start_date", "")
|
||||||
end_date = self.config.get("end_date", "")
|
end_date = self.config.get("end_date", "")
|
||||||
|
|
||||||
# Search for token to get proper token_id (contract address)
|
# Get token address from strategy config (saved when user confirmed token)
|
||||||
try:
|
token_address = None
|
||||||
tokens = await self.ave_client.get_tokens(query=token, chain=chain, limit=10)
|
token_symbol = None
|
||||||
if not tokens:
|
|
||||||
raise ValueError(f"Token '{token}' not found on {chain}")
|
# Try to get from conditions first
|
||||||
|
if self.conditions:
|
||||||
# Find matching token
|
token_address = self.conditions[0].get("token_address")
|
||||||
token_info = None
|
token_symbol = self.conditions[0].get("token")
|
||||||
for t in tokens:
|
# Fallback to actions
|
||||||
if t.get("symbol", "").upper() == token.upper() or t.get("name", "").upper() == token.upper():
|
if not token_address and self.actions:
|
||||||
token_info = t
|
token_address = self.actions[0].get("token_address")
|
||||||
break
|
token_symbol = self.actions[0].get("token") or token_symbol
|
||||||
|
|
||||||
if not token_info:
|
if not token_address:
|
||||||
# Use first result if exact match not found
|
raise ValueError("Token address not found in strategy. Please update your strategy with a valid token.")
|
||||||
token_info = tokens[0]
|
|
||||||
|
token_id = token_address
|
||||||
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
|
|
||||||
|
|
||||||
start_ts = None
|
start_ts = None
|
||||||
end_ts = None
|
end_ts = None
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ export interface StrategyConfig {
|
|||||||
export interface Condition {
|
export interface Condition {
|
||||||
type: 'price_drop' | 'price_rise' | 'volume_spike' | 'price_level';
|
type: 'price_drop' | 'price_rise' | 'volume_spike' | 'price_level';
|
||||||
token: string;
|
token: string;
|
||||||
|
token_address?: string;
|
||||||
chain?: string;
|
chain?: string;
|
||||||
threshold?: number;
|
threshold?: number;
|
||||||
price?: number;
|
price?: number;
|
||||||
@@ -37,6 +38,7 @@ export interface Action {
|
|||||||
type: 'buy' | 'sell' | 'hold';
|
type: 'buy' | 'sell' | 'hold';
|
||||||
amount_percent?: number;
|
amount_percent?: number;
|
||||||
token?: string;
|
token?: string;
|
||||||
|
token_address?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RiskManagement {
|
export interface RiskManagement {
|
||||||
|
|||||||
@@ -32,15 +32,6 @@
|
|||||||
if ($isAuthenticated && botId) {
|
if ($isAuthenticated && botId) {
|
||||||
await loadBot();
|
await loadBot();
|
||||||
await loadBacktests();
|
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>
|
||||||
|
|
||||||
<section class="results-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}
|
{#if $backtestStore.backtestHistory.length === 0}
|
||||||
<p class="empty-state">No backtests yet. Run your first backtest above.</p>
|
<p class="empty-state">No backtests yet. Run your first backtest above.</p>
|
||||||
@@ -265,7 +261,40 @@
|
|||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
font-size: 1.25rem;
|
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 {
|
.content {
|
||||||
|
|||||||
Reference in New Issue
Block a user