Problem
When the Reflector oracle is unavailable or returns stale data, ReflectorService in backend/src/services/reflector.ts falls back to random price variation instead of using the last known good price. This makes portfolio values jitter randomly whenever the oracle hiccups.
The fallback logic does something like:
// When Reflector is unavailable
const basePrice = lastKnownPrice || hardcodedPrice;
const variation = (Math.random() - 0.5) * 0.02; // ±1% random noise
return basePrice * (1 + variation);
This means:
- Portfolio values fluctuate even when no trades happen
- Rebalancing triggers can fire based on phantom price movements (the random variation might push a portfolio past its drift threshold)
- Risk metrics (VaR, volatility) are computed on noise, not real data
- The circuit breaker might trigger unnecessarily
Proposed Fix
1. Use last known price with no variation
When the oracle is down, return the last successfully fetched price with zero variation:
async getPrice(asset: string): Promise<PriceData> {
try {
const price = await this.fetchFromReflector(asset);
this.cachePrice(asset, price); // store for fallback
return price;
} catch (error) {
const cached = this.getLastKnownPrice(asset);
if (cached) {
logger.warn(`Reflector unavailable for ${asset}, using cached price from ${cached.timestamp}`);
return { ...cached, source: 'cached', stale: true };
}
throw new Error(`No price data available for ${asset}`);
}
}
2. Track staleness
Each cached price should have a timestamp. If the cached price is older than a configurable threshold (e.g., 1 hour), mark it as stale: true so downstream consumers can decide how to handle it:
- Portfolio value calculations: use stale prices but show a "stale data" indicator
- Rebalancing decisions: skip rebalancing if any asset price is stale (don't trade on bad data)
- Risk metrics: pause VaR/volatility calculations if prices are stale
3. Cache prices in the database
Currently prices might only be cached in memory. Store them in PostgreSQL so they survive restarts:
CREATE TABLE IF NOT EXISTS price_cache (
asset VARCHAR(32) PRIMARY KEY,
price NUMERIC(20, 8) NOT NULL,
source VARCHAR(32) NOT NULL,
fetched_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
The analyticsSnapshotWorker already runs periodically — have it also refresh the price cache.
4. Graceful degradation in the frontend
When the backend returns prices with stale: true, the frontend should:
- Show a yellow warning banner: "Price data may be outdated"
- Disable the "Rebalance Now" button
- Still show portfolio value but with a lower opacity or asterisk
Files to modify
backend/src/services/reflector.ts — replace random variation with cached fallback, add staleness tracking
backend/src/db/ — add price_cache table and query methods
backend/src/services/rebalancing.ts — skip rebalancing when prices are stale
backend/src/services/riskManagements.ts — pause risk calculations when prices are stale
frontend/src/components/Dashboard.tsx — show stale data indicator
frontend/src/components/PriceTracker.tsx — show last-updated timestamp
Acceptance Criteria
References
Affected Area
Backend, Frontend
Problem
When the Reflector oracle is unavailable or returns stale data,
ReflectorServiceinbackend/src/services/reflector.tsfalls back to random price variation instead of using the last known good price. This makes portfolio values jitter randomly whenever the oracle hiccups.The fallback logic does something like:
This means:
Proposed Fix
1. Use last known price with no variation
When the oracle is down, return the last successfully fetched price with zero variation:
2. Track staleness
Each cached price should have a timestamp. If the cached price is older than a configurable threshold (e.g., 1 hour), mark it as
stale: trueso downstream consumers can decide how to handle it:3. Cache prices in the database
Currently prices might only be cached in memory. Store them in PostgreSQL so they survive restarts:
The
analyticsSnapshotWorkeralready runs periodically — have it also refresh the price cache.4. Graceful degradation in the frontend
When the backend returns prices with
stale: true, the frontend should:Files to modify
backend/src/services/reflector.ts— replace random variation with cached fallback, add staleness trackingbackend/src/db/— addprice_cachetable and query methodsbackend/src/services/rebalancing.ts— skip rebalancing when prices are stalebackend/src/services/riskManagements.ts— pause risk calculations when prices are stalefrontend/src/components/Dashboard.tsx— show stale data indicatorfrontend/src/components/PriceTracker.tsx— show last-updated timestampAcceptance Criteria
stale: trueReferences
backend/src/services/reflector.tsbackend/src/services/riskManagements.tsbackend/src/services/rebalancing.tsAffected Area
Backend, Frontend