feat: implement token address confirmation dialog and limit backtest duration

This commit is contained in:
shokollm
2026-04-10 11:52:40 +00:00
parent f86ff75525
commit 297a185215
5 changed files with 190 additions and 9 deletions

View File

@@ -223,6 +223,8 @@ def chat(
thinking=result.get("thinking"), thinking=result.get("thinking"),
strategy_config=bot.strategy_config if result.get("strategy_updated") else None, strategy_config=bot.strategy_config if result.get("strategy_updated") else None,
success=result.get("success", False), success=result.get("success", False),
strategy_needs_confirmation=result.get("strategy_needs_confirmation", False),
strategy_data=result.get("strategy_data") if result.get("strategy_needs_confirmation") else None,
) )

View File

@@ -149,6 +149,8 @@ class BotChatResponse(BaseModel):
thinking: Optional[str] = None thinking: Optional[str] = None
strategy_config: Optional[dict] = None strategy_config: Optional[dict] = None
success: bool = False success: bool = False
strategy_needs_confirmation: Optional[bool] = False
strategy_data: Optional[dict] = None
class SignalResponse(BaseModel): class SignalResponse(BaseModel):

View File

@@ -130,4 +130,6 @@ export interface BotChatResponse {
thinking: string | null; thinking: string | null;
strategy_config: StrategyConfig | null; strategy_config: StrategyConfig | null;
success: boolean; success: boolean;
strategy_needs_confirmation?: boolean;
strategy_data?: StrategyConfig | null;
} }

View File

@@ -10,6 +10,12 @@
let isSending = $state(false); let isSending = $state(false);
let showStrategy = $state(false); let showStrategy = $state(false);
// Token address confirmation modal state
let showTokenConfirm = $state(false);
let pendingStrategyData = $state<any>(null);
let tokenAddressInput = $state('');
let confirmingMessage = $state('');
onMount(async () => { onMount(async () => {
if (!$isAuthenticated && !$isLoading) { if (!$isAuthenticated && !$isLoading) {
goto('/login'); goto('/login');
@@ -55,6 +61,15 @@
const response = await api.bots.chat(botId, message, controller.signal); const response = await api.bots.chat(botId, message, controller.signal);
clearTimeout(timeoutId); clearTimeout(timeoutId);
// Check if token address confirmation is needed
if (response.strategy_needs_confirmation && response.strategy_data) {
// Show token confirmation modal
pendingStrategyData = response.strategy_data;
confirmingMessage = response.response;
tokenAddressInput = '';
showTokenConfirm = true;
}
// Add assistant response with thinking // Add assistant response with thinking
addMessage({ role: 'assistant', content: response.response, thinking: response.thinking || null }); addMessage({ role: 'assistant', content: response.response, thinking: response.thinking || null });
@@ -76,6 +91,56 @@
function toggleStrategy() { function toggleStrategy() {
showStrategy = !showStrategy; showStrategy = !showStrategy;
} }
async function confirmTokenAddress() {
if (!tokenAddressInput.trim() || !pendingStrategyData) {
showTokenConfirm = false;
return;
}
// Update the pending strategy with the token address
const updatedStrategy = { ...pendingStrategyData };
// Update conditions with token address
if (updatedStrategy.conditions) {
updatedStrategy.conditions = updatedStrategy.conditions.map((cond: any) => ({
...cond,
token_address: tokenAddressInput.trim()
}));
}
// Update actions with token address
if (updatedStrategy.actions) {
updatedStrategy.actions = updatedStrategy.actions.map((action: any) => ({
...action,
token_address: tokenAddressInput.trim()
}));
}
try {
// Update bot with the strategy
await api.bots.update(botId, { strategy_config: updatedStrategy });
// Refresh bot data
const bot = await api.bots.get(botId);
setCurrentBot(bot);
// Add success message
addMessage({ role: 'assistant', content: `Perfect! I've saved your strategy with the token address. You can now run backtests!`, thinking: null });
} catch (e) {
addMessage({ role: 'assistant', content: 'Failed to save strategy. Please try again.', thinking: null });
}
showTokenConfirm = false;
pendingStrategyData = null;
tokenAddressInput = '';
}
function cancelTokenConfirm() {
showTokenConfirm = false;
pendingStrategyData = null;
tokenAddressInput = '';
}
</script> </script>
<svelte:head> <svelte:head>
@@ -83,6 +148,20 @@
</svelte:head> </svelte:head>
<main> <main>
{#if showTokenConfirm}
<div class="modal-overlay" onclick={cancelTokenConfirm}>
<div class="modal-content" onclick={(e) => e.stopPropagation()}>
<h3>Confirm Token Address</h3>
<p class="modal-message">{confirmingMessage}</p>
<p class="modal-hint">Please enter the BSC contract address for the token:</p>
<input type="text" class="token-input" bind:value={tokenAddressInput} placeholder="0x..."/>
<div class="modal-actions">
<button class="btn btn-secondary" onclick={cancelTokenConfirm}>Cancel</button>
<button class="btn btn-primary" onclick={confirmTokenAddress} disabled={!tokenAddressInput.trim()}>Confirm</button>
</div>
</div>
</div>
{/if}
<header> <header>
<div class="header-left"> <div class="header-left">
<a href="/dashboard" class="back-link">← Dashboard</a> <a href="/dashboard" class="back-link">← Dashboard</a>
@@ -200,4 +279,88 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
/* Modal Styles */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.7);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.modal-content {
background: rgba(20, 20, 20, 0.95);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 16px;
padding: 1.5rem;
max-width: 450px;
width: 90%;
}
.modal-content h3 {
margin: 0 0 1rem;
color: #667eea;
}
.modal-message {
color: #ccc;
margin-bottom: 0.5rem;
line-height: 1.5;
}
.modal-hint {
color: #888;
font-size: 0.9rem;
margin-bottom: 1rem;
}
.token-input {
width: 100%;
padding: 0.75rem;
border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.2);
background: rgba(255, 255, 255, 0.05);
color: #fff;
font-size: 1rem;
font-family: 'Monaco', 'Menlo', monospace;
box-sizing: border-box;
}
.token-input:focus {
outline: none;
border-color: #667eea;
}
.modal-actions {
display: flex;
gap: 0.75rem;
margin-top: 1rem;
justify-content: flex-end;
}
.btn-primary {
padding: 0.75rem 1.5rem;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 8px;
font-size: 1rem;
font-weight: 500;
cursor: pointer;
}
.btn-primary:hover:not(:disabled) {
transform: translateY(-2px);
}
.btn-primary:disabled {
opacity: 0.5;
cursor: not-allowed;
}
</style> </style>

View File

@@ -16,14 +16,15 @@
let selectedBacktest = $state<Backtest | null>(null); let selectedBacktest = $state<Backtest | null>(null);
onMount(async () => { onMount(async () => {
// Set default dates (yesterday to 30 days ago) // Set default dates - yesterday only (1 day range for fast testing)
const yesterday = new Date(); const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1); yesterday.setDate(yesterday.getDate() - 1);
const thirtyDaysAgo = new Date();
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
endDate = yesterday.toISOString().split('T')[0]; // Set max date to yesterday
startDate = thirtyDaysAgo.toISOString().split('T')[0]; const maxDate = yesterday.toISOString().split('T')[0];
endDate = maxDate;
startDate = maxDate; // Same day = 1 day backtest
if (!$isAuthenticated && !$isLoading) { if (!$isAuthenticated && !$isLoading) {
goto('/login'); goto('/login');
@@ -55,6 +56,17 @@
async function startBacktest() { async function startBacktest() {
if (!startDate || !endDate) return; if (!startDate || !endDate) return;
// Validate date range (max 7 days)
const start = new Date(startDate);
const end = new Date(endDate);
const daysDiff = Math.ceil((end.getTime() - start.getTime()) / (1000 * 60 * 60 * 24));
if (daysDiff > 7) {
setBacktestError('Maximum backtest duration is 7 days for fast testing');
return;
}
setBacktestError(null); setBacktestError(null);
setBacktestLoading(true); setBacktestLoading(true);
isRunning = true; isRunning = true;
@@ -125,10 +137,10 @@
<div class="field"> <div class="field">
<label for="timeframe">Timeframe</label> <label for="timeframe">Timeframe</label>
<select id="timeframe" bind:value={timeframe}> <select id="timeframe" bind:value={timeframe}>
<option value="1m">1 minute</option>
<option value="5m">5 minutes</option>
<option value="15m">15 minutes</option>
<option value="1h">1 hour</option> <option value="1h">1 hour (recommended)</option>
<option value="4h">4 hours</option> <option value="4h">4 hours</option>
<option value="1d">1 day</option> <option value="1d">1 day</option>
</select> </select>