This commit is contained in:
Dobromir Popov
2025-11-22 01:47:53 +02:00
parent 21813fbfe3
commit d4c0483675
4 changed files with 34 additions and 18 deletions

View File

@@ -2579,8 +2579,9 @@ class RealTrainingAdapter:
for tf in ['1s', '1m', '1h', '1d']: for tf in ['1s', '1m', '1h', '1d']:
# Get historical data (raw) # Get historical data (raw)
# Force refresh for 1s/1m to ensure we have the very latest candle for prediction # Force refresh for 1s/1m to ensure we have the very latest candle for prediction
# But set persist=False to avoid locking the database with high-frequency writes
refresh = tf in ['1s', '1m'] refresh = tf in ['1s', '1m']
df = data_provider.get_historical_data(symbol, tf, limit=600, refresh=refresh) df = data_provider.get_historical_data(symbol, tf, limit=600, refresh=refresh, persist=False)
if df is not None and not df.empty: if df is not None and not df.empty:
# Extract raw arrays # Extract raw arrays
opens = df['open'].values.astype(np.float32) opens = df['open'].values.astype(np.float32)

View File

@@ -1902,10 +1902,11 @@ class ChartManager {
// Add prediction traces (ghost candles) // Add prediction traces (ghost candles)
if (predictionTraces.length > 0) { if (predictionTraces.length > 0) {
// Remove existing ghost traces first (heuristic: traces with name 'Ghost Prediction') // Remove existing ghost traces safely
// We iterate backwards to avoid index shifting issues when deleting
const currentTraces = plotElement.data.length; const currentTraces = plotElement.data.length;
const indicesToRemove = []; const indicesToRemove = [];
for (let i = 0; i < currentTraces; i++) { for (let i = currentTraces - 1; i >= 0; i--) {
if (plotElement.data[i].name === 'Ghost Prediction') { if (plotElement.data[i].name === 'Ghost Prediction') {
indicesToRemove.push(i); indicesToRemove.push(i);
} }

View File

@@ -849,8 +849,8 @@
return; return;
} }
// Display last 5 predictions (most recent first) // Display last 15 predictions (most recent first)
const html = predictionHistory.slice(0, 5).map(pred => { const html = predictionHistory.slice(0, 15).map(pred => {
// Safely parse timestamp // Safely parse timestamp
let timeStr = '--:--:--'; let timeStr = '--:--:--';
try { try {
@@ -868,12 +868,14 @@
pred.action === 'SELL' ? 'text-danger' : 'text-secondary'; pred.action === 'SELL' ? 'text-danger' : 'text-secondary';
const confidence = (pred.confidence * 100).toFixed(1); const confidence = (pred.confidence * 100).toFixed(1);
const price = (pred.predicted_price && !isNaN(pred.predicted_price)) ? pred.predicted_price.toFixed(2) : '--'; const price = (pred.predicted_price && !isNaN(pred.predicted_price)) ? pred.predicted_price.toFixed(2) : '--';
const timeframe = pred.timeframe || '1m';
return ` return `
<div class="d-flex justify-content-between align-items-center mb-1 pb-1 border-bottom"> <div class="d-flex justify-content-between align-items-center mb-1 pb-1 border-bottom">
<div> <div>
<span class="badge bg-dark text-light me-1" style="font-size: 0.6rem;">${timeframe}</span>
<span class="${actionColor} fw-bold">${pred.action}</span> <span class="${actionColor} fw-bold">${pred.action}</span>
<span class="text-muted ms-1">${timeStr}</span> <span class="text-muted ms-1" style="font-size: 0.75rem;">${timeStr}</span>
</div> </div>
<div class="text-end"> <div class="text-end">
<div>${confidence}%</div> <div>${confidence}%</div>
@@ -930,17 +932,23 @@
timestamp = latest.timestamp; timestamp = latest.timestamp;
} }
// Add to prediction history (keep last 5) // Add to prediction history (keep last 15)
predictionHistory.unshift({ const newPrediction = {
timestamp: timestamp, timestamp: timestamp,
action: latest.action, action: latest.action,
confidence: latest.confidence, confidence: latest.confidence,
predicted_price: latest.predicted_price predicted_price: latest.predicted_price,
}); timeframe: appState.currentTimeframes ? appState.currentTimeframes[0] : '1m'
if (predictionHistory.length > 5) { };
predictionHistory = predictionHistory.slice(0, 5);
// Filter out undefined/invalid predictions before adding
if (latest.action && !isNaN(latest.confidence)) {
predictionHistory.unshift(newPrediction);
if (predictionHistory.length > 15) {
predictionHistory = predictionHistory.slice(0, 15);
}
updatePredictionHistory();
} }
updatePredictionHistory();
// Update chart with signal markers and predictions // Update chart with signal markers and predictions
if (window.appState && window.appState.chartManager) { if (window.appState && window.appState.chartManager) {

View File

@@ -1623,7 +1623,7 @@ class DataProvider:
logger.error(f"Error getting market state at time: {e}") logger.error(f"Error getting market state at time: {e}")
return {} return {}
def get_historical_data(self, symbol: str, timeframe: str, limit: int = 1000, refresh: bool = False, allow_stale_cache: bool = False) -> Optional[pd.DataFrame]: def get_historical_data(self, symbol: str, timeframe: str, limit: int = 1000, refresh: bool = False, allow_stale_cache: bool = False, persist: bool = True) -> Optional[pd.DataFrame]:
"""Get historical OHLCV data. """Get historical OHLCV data.
- Prefer cached data for low latency. - Prefer cached data for low latency.
- If cache is empty or refresh=True, fetch real data from exchanges. - If cache is empty or refresh=True, fetch real data from exchanges.
@@ -1635,6 +1635,7 @@ class DataProvider:
limit: Number of candles to return limit: Number of candles to return
refresh: Force refresh from exchange refresh: Force refresh from exchange
allow_stale_cache: Allow loading stale cache (for startup performance) allow_stale_cache: Allow loading stale cache (for startup performance)
persist: Whether to save fetched data to DuckDB (default: True). Set False for high-frequency polling.
""" """
try: try:
# Serve from cache when available # Serve from cache when available
@@ -1644,7 +1645,7 @@ class DataProvider:
return cached_df.tail(limit) return cached_df.tail(limit)
# Try loading from DuckDB first (fast Parquet queries) # Try loading from DuckDB first (fast Parquet queries)
if allow_stale_cache: if allow_stale_cache and not refresh:
cached_df = self._load_from_duckdb(symbol, timeframe, limit=1500) cached_df = self._load_from_duckdb(symbol, timeframe, limit=1500)
if cached_df is not None and not cached_df.empty: if cached_df is not None and not cached_df.empty:
logger.info(f"Loaded {len(cached_df)} candles from DuckDB for {symbol} {timeframe} (startup mode)") logger.info(f"Loaded {len(cached_df)} candles from DuckDB for {symbol} {timeframe} (startup mode)")
@@ -1662,8 +1663,8 @@ class DataProvider:
if df is not None and not df.empty: if df is not None and not df.empty:
df = self._ensure_datetime_index(df) df = self._ensure_datetime_index(df)
# Store in DuckDB (Parquet + SQL in one) # Store in DuckDB (Parquet + SQL in one) - Only if persist is True
if self.duckdb_storage: if self.duckdb_storage and persist:
try: try:
self.duckdb_storage.store_ohlcv_data(symbol, timeframe, df) self.duckdb_storage.store_ohlcv_data(symbol, timeframe, df)
except Exception as e: except Exception as e:
@@ -1680,7 +1681,12 @@ class DataProvider:
combined_df = combined_df.sort_index() combined_df = combined_df.sort_index()
self.cached_data[symbol][timeframe] = combined_df.tail(1500) self.cached_data[symbol][timeframe] = combined_df.tail(1500)
logger.info(f"Stored {len(df)} candles for {symbol} {timeframe} (DuckDB + memory cache)") if persist:
logger.info(f"Stored {len(df)} candles for {symbol} {timeframe} (DuckDB + memory cache)")
else:
# Less verbose for high-frequency polling
logger.debug(f"Updated memory cache with {len(df)} candles for {symbol} {timeframe}")
return self.cached_data[symbol][timeframe].tail(limit) return self.cached_data[symbol][timeframe].tail(limit)
logger.warning(f"No real data available for {symbol} {timeframe} at request time") logger.warning(f"No real data available for {symbol} {timeframe} at request time")