Improve dashboard rendering stability and increase DB pool size
This commit is contained in:
@ -4,6 +4,7 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>BTC Trading Dashboard</title>
|
||||
<link rel="icon" href="data:,">
|
||||
<script src="https://unpkg.com/lightweight-charts@4.1.0/dist/lightweight-charts.standalone.production.js"></script>
|
||||
<style>
|
||||
:root {
|
||||
@ -492,11 +493,29 @@
|
||||
this.loadInitialData();
|
||||
this.loadTA();
|
||||
|
||||
setInterval(() => this.loadNewData(), 15000);
|
||||
// Refresh every 10 seconds for new data
|
||||
setInterval(() => {
|
||||
this.loadNewData();
|
||||
// Occasionally refresh TA
|
||||
if (new Date().getSeconds() < 15) this.loadTA();
|
||||
}, 10000);
|
||||
}
|
||||
|
||||
isAtRightEdge() {
|
||||
const timeScale = this.chart.timeScale();
|
||||
const visibleRange = timeScale.getVisibleLogicalRange();
|
||||
if (!visibleRange) return true;
|
||||
|
||||
const data = this.candleSeries.data();
|
||||
if (!data || data.length === 0) return true;
|
||||
|
||||
// If the right-most visible bar is within 5 bars of the last data point
|
||||
return visibleRange.to >= data.length - 5;
|
||||
}
|
||||
|
||||
createTimeframeButtons() {
|
||||
const container = document.getElementById('timeframeContainer');
|
||||
container.innerHTML = ''; // Clear existing
|
||||
this.intervals.forEach(interval => {
|
||||
const btn = document.createElement('button');
|
||||
btn.className = 'timeframe-btn';
|
||||
@ -527,10 +546,17 @@
|
||||
},
|
||||
rightPriceScale: {
|
||||
borderColor: '#2a2e39',
|
||||
autoScale: true,
|
||||
},
|
||||
timeScale: {
|
||||
borderColor: '#2a2e39',
|
||||
timeVisible: true,
|
||||
secondsVisible: false,
|
||||
rightOffset: 12,
|
||||
barSpacing: 10,
|
||||
},
|
||||
handleScroll: {
|
||||
vertTouchDrag: false,
|
||||
},
|
||||
});
|
||||
|
||||
@ -551,6 +577,18 @@
|
||||
height: chartContainer.clientHeight,
|
||||
});
|
||||
});
|
||||
|
||||
// Handle tab visibility and focus
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
if (document.visibilityState === 'visible') {
|
||||
this.loadNewData();
|
||||
this.loadTA();
|
||||
}
|
||||
});
|
||||
window.addEventListener('focus', () => {
|
||||
this.loadNewData();
|
||||
this.loadTA();
|
||||
});
|
||||
}
|
||||
|
||||
initEventListeners() {
|
||||
@ -570,11 +608,11 @@
|
||||
}
|
||||
|
||||
async loadInitialData() {
|
||||
await this.loadData(500, true);
|
||||
await this.loadData(1000, true);
|
||||
this.hasInitialLoad = true;
|
||||
}
|
||||
|
||||
async loadData(limit = 500, fitToContent = false) {
|
||||
async loadData(limit = 1000, fitToContent = false) {
|
||||
if (this.isLoading) return;
|
||||
this.isLoading = true;
|
||||
|
||||
@ -616,13 +654,21 @@
|
||||
}
|
||||
|
||||
async loadNewData() {
|
||||
if (!this.hasInitialLoad) return;
|
||||
if (!this.hasInitialLoad || this.isLoading) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/v1/candles?symbol=BTC&interval=${this.currentInterval}&limit=100`);
|
||||
const response = await fetch(`/api/v1/candles?symbol=BTC&interval=${this.currentInterval}&limit=50`);
|
||||
const data = await response.json();
|
||||
|
||||
if (data.candles && data.candles.length > 0) {
|
||||
const atEdge = this.isAtRightEdge();
|
||||
|
||||
// Get current data from series to find the last timestamp
|
||||
const currentSeriesData = this.candleSeries.data();
|
||||
const lastTimestamp = currentSeriesData.length > 0
|
||||
? currentSeriesData[currentSeriesData.length - 1].time
|
||||
: 0;
|
||||
|
||||
const chartData = data.candles.reverse().map(c => ({
|
||||
time: Math.floor(new Date(c.time).getTime() / 1000),
|
||||
open: parseFloat(c.open),
|
||||
@ -631,13 +677,24 @@
|
||||
close: parseFloat(c.close)
|
||||
}));
|
||||
|
||||
// Only update with data that is newer than or equal to the last candle
|
||||
// update() handles equal timestamps by updating the existing bar
|
||||
chartData.forEach(candle => {
|
||||
if (candle.time >= lastTimestamp) {
|
||||
this.candleSeries.update(candle);
|
||||
}
|
||||
});
|
||||
|
||||
// Update cache
|
||||
const existingData = this.allData.get(this.currentInterval) || [];
|
||||
const mergedData = this.mergeData(existingData, chartData);
|
||||
this.allData.set(this.currentInterval, mergedData);
|
||||
this.allData.set(this.currentInterval, this.mergeData(existingData, chartData));
|
||||
|
||||
this.candleSeries.setData(mergedData);
|
||||
// If we were at the edge, scroll to show the new candle
|
||||
if (atEdge) {
|
||||
this.chart.timeScale().scrollToRealTime();
|
||||
}
|
||||
|
||||
const latest = mergedData[mergedData.length - 1];
|
||||
const latest = chartData[chartData.length - 1];
|
||||
this.updateStats(latest);
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@ -39,8 +39,9 @@ async def get_db_pool():
|
||||
database=DB_NAME,
|
||||
user=DB_USER,
|
||||
password=DB_PASSWORD,
|
||||
min_size=1,
|
||||
max_size=10
|
||||
min_size=2,
|
||||
max_size=20,
|
||||
max_inactive_connection_lifetime=300
|
||||
)
|
||||
|
||||
|
||||
|
||||
@ -28,14 +28,14 @@ class DatabaseManager:
|
||||
database: str = None,
|
||||
user: str = None,
|
||||
password: str = None,
|
||||
pool_size: int = 5
|
||||
pool_size: int = 20
|
||||
):
|
||||
self.host = host or os.getenv('DB_HOST', 'localhost')
|
||||
self.port = port or int(os.getenv('DB_PORT', 5432))
|
||||
self.database = database or os.getenv('DB_NAME', 'btc_data')
|
||||
self.user = user or os.getenv('DB_USER', 'btc_bot')
|
||||
self.password = password or os.getenv('DB_PASSWORD', '')
|
||||
self.pool_size = pool_size
|
||||
self.pool_size = int(os.getenv('DB_POOL_SIZE', pool_size))
|
||||
|
||||
self.pool: Optional[Pool] = None
|
||||
|
||||
@ -48,17 +48,18 @@ class DatabaseManager:
|
||||
database=self.database,
|
||||
user=self.user,
|
||||
password=self.password,
|
||||
min_size=1,
|
||||
min_size=2,
|
||||
max_size=self.pool_size,
|
||||
command_timeout=60
|
||||
command_timeout=60,
|
||||
max_inactive_connection_lifetime=300
|
||||
)
|
||||
|
||||
# Test connection
|
||||
async with self.pool.acquire() as conn:
|
||||
async with self.acquire() as conn:
|
||||
version = await conn.fetchval('SELECT version()')
|
||||
logger.info(f"Connected to database: {version[:50]}...")
|
||||
|
||||
logger.info(f"Database pool created (size: {self.pool_size})")
|
||||
logger.info(f"Database pool created (min: 2, max: {self.pool_size})")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to connect to database: {type(e).__name__}: {e!r}")
|
||||
@ -71,12 +72,16 @@ class DatabaseManager:
|
||||
logger.info("Database pool closed")
|
||||
|
||||
@asynccontextmanager
|
||||
async def acquire(self):
|
||||
"""Context manager for acquiring connection"""
|
||||
async def acquire(self, timeout: float = 30.0):
|
||||
"""Context manager for acquiring connection with timeout"""
|
||||
if not self.pool:
|
||||
raise RuntimeError("Database not connected")
|
||||
async with self.pool.acquire() as conn:
|
||||
try:
|
||||
async with self.pool.acquire(timeout=timeout) as conn:
|
||||
yield conn
|
||||
except asyncio.TimeoutError:
|
||||
logger.error(f"Database connection acquisition timed out after {timeout}s")
|
||||
raise
|
||||
|
||||
async def insert_candles(self, candles: List[Candle]) -> int:
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user