feat: implement token address confirmation dialog and limit backtest duration
This commit is contained in:
@@ -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,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,12 @@
|
|||||||
let botId = $derived($page.params.id);
|
let botId = $derived($page.params.id);
|
||||||
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) {
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user