From 297a185215c46dc3849211a601642226c569c944 Mon Sep 17 00:00:00 2001 From: shokollm <270575765+shokollm@users.noreply.github.com> Date: Fri, 10 Apr 2026 11:52:40 +0000 Subject: [PATCH] feat: implement token address confirmation dialog and limit backtest duration --- src/backend/app/api/bots.py | 2 + src/backend/app/db/schemas.py | 2 + src/frontend/src/lib/api/types.ts | 2 + src/frontend/src/routes/bot/[id]/+page.svelte | 163 ++++++++++++++++++ .../src/routes/bot/[id]/backtest/+page.svelte | 30 +++- 5 files changed, 190 insertions(+), 9 deletions(-) diff --git a/src/backend/app/api/bots.py b/src/backend/app/api/bots.py index 4755e95..36d6ed9 100644 --- a/src/backend/app/api/bots.py +++ b/src/backend/app/api/bots.py @@ -223,6 +223,8 @@ def chat( thinking=result.get("thinking"), strategy_config=bot.strategy_config if result.get("strategy_updated") else None, 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, ) diff --git a/src/backend/app/db/schemas.py b/src/backend/app/db/schemas.py index fffe017..467f95d 100644 --- a/src/backend/app/db/schemas.py +++ b/src/backend/app/db/schemas.py @@ -149,6 +149,8 @@ class BotChatResponse(BaseModel): thinking: Optional[str] = None strategy_config: Optional[dict] = None success: bool = False + strategy_needs_confirmation: Optional[bool] = False + strategy_data: Optional[dict] = None class SignalResponse(BaseModel): diff --git a/src/frontend/src/lib/api/types.ts b/src/frontend/src/lib/api/types.ts index 7df69c9..8be11cd 100644 --- a/src/frontend/src/lib/api/types.ts +++ b/src/frontend/src/lib/api/types.ts @@ -130,4 +130,6 @@ export interface BotChatResponse { thinking: string | null; strategy_config: StrategyConfig | null; success: boolean; + strategy_needs_confirmation?: boolean; + strategy_data?: StrategyConfig | null; } diff --git a/src/frontend/src/routes/bot/[id]/+page.svelte b/src/frontend/src/routes/bot/[id]/+page.svelte index 1fd5d5a..ce6a960 100644 --- a/src/frontend/src/routes/bot/[id]/+page.svelte +++ b/src/frontend/src/routes/bot/[id]/+page.svelte @@ -9,6 +9,12 @@ let botId = $derived($page.params.id); let isSending = $state(false); let showStrategy = $state(false); + + // Token address confirmation modal state + let showTokenConfirm = $state(false); + let pendingStrategyData = $state(null); + let tokenAddressInput = $state(''); + let confirmingMessage = $state(''); onMount(async () => { if (!$isAuthenticated && !$isLoading) { @@ -55,6 +61,15 @@ const response = await api.bots.chat(botId, message, controller.signal); 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 addMessage({ role: 'assistant', content: response.response, thinking: response.thinking || null }); @@ -76,6 +91,56 @@ function toggleStrategy() { 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 = ''; + } @@ -83,6 +148,20 @@
+ {#if showTokenConfirm} + + {/if}
← Dashboard @@ -200,4 +279,88 @@ display: flex; 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; + } \ No newline at end of file diff --git a/src/frontend/src/routes/bot/[id]/backtest/+page.svelte b/src/frontend/src/routes/bot/[id]/backtest/+page.svelte index 4205507..48ca126 100644 --- a/src/frontend/src/routes/bot/[id]/backtest/+page.svelte +++ b/src/frontend/src/routes/bot/[id]/backtest/+page.svelte @@ -16,14 +16,15 @@ let selectedBacktest = $state(null); 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(); yesterday.setDate(yesterday.getDate() - 1); - const thirtyDaysAgo = new Date(); - thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30); - endDate = yesterday.toISOString().split('T')[0]; - startDate = thirtyDaysAgo.toISOString().split('T')[0]; + // Set max date to yesterday + const maxDate = yesterday.toISOString().split('T')[0]; + + endDate = maxDate; + startDate = maxDate; // Same day = 1 day backtest if (!$isAuthenticated && !$isLoading) { goto('/login'); @@ -55,6 +56,17 @@ async function startBacktest() { 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); setBacktestLoading(true); isRunning = true; @@ -125,10 +137,10 @@