From 48b0098c6fddda74e524dfebe6e241005ef51d3d Mon Sep 17 00:00:00 2001 From: Zach Harding Date: Tue, 26 May 2026 11:31:19 -0400 Subject: [PATCH] equal height columns for card images --- src/pages/partials/cards.astro | 4 +-- src/volatility.ts | 61 ++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 src/volatility.ts diff --git a/src/pages/partials/cards.astro b/src/pages/partials/cards.astro index 3cee967..e870868 100644 --- a/src/pages/partials/cards.astro +++ b/src/pages/partials/cards.astro @@ -327,14 +327,14 @@ const facets = searchResults.results.slice(1).map((result: any) => { )} {pokemon.map((card:any) => ( -
+
{canAddInventory && ( )}
-
{card.productName} +
{card.productName}
diff --git a/src/volatility.ts b/src/volatility.ts new file mode 100644 index 0000000..36ea2c9 --- /dev/null +++ b/src/volatility.ts @@ -0,0 +1,61 @@ +export interface VolatilityResult { + label: 'High' | 'Medium' | 'Low' | '—'; + monthlyVol: number; +} + +/** + * Computes 30-day rolling volatility from an array of prices using + * log-return standard deviation scaled to a monthly expectation. + * + * @param prices - Ordered array of market prices (oldest → newest) + * @returns label ('High' | 'Medium' | 'Low' | '—') and monthlyVol (0–1 range) + */ +export function computeVolatility(prices: number[]): VolatilityResult { + if (prices.length < 2) return { label: '—', monthlyVol: 0 }; + + const returns: number[] = []; + for (let i = 1; i < prices.length; i++) { + returns.push(Math.log(prices[i] / prices[i - 1])); + } + + const mean = returns.reduce((a, b) => a + b, 0) / returns.length; + const variance = returns.reduce((sum, r) => sum + (r - mean) ** 2, 0) / (returns.length - 1); + const monthlyVol = Math.sqrt(variance) * Math.sqrt(30); + + const label: VolatilityResult['label'] = + monthlyVol >= 0.30 ? 'High' + : monthlyVol >= 0.15 ? 'Medium' + : 'Low'; + + return { label, monthlyVol: Math.round(monthlyVol * 100) / 100 }; +} + +/** + * Groups a list of price-history rows by condition and computes volatility + * for each, filtered to a rolling window. + * + * @param rows - Array of { condition, calculatedAt, marketPrice } + * @param windowMs - Rolling window in milliseconds (default: 30 days) + */ +export function volatilityByCondition( + rows: Array<{ condition: string; calculatedAt: string | Date | null; marketPrice: number | string | null }>, + windowMs = 30 * 86_400_000, +): Record { + const cutoff = new Date(Date.now() - windowMs); + const grouped: Record = {}; + + for (const row of rows) { + if (row.marketPrice == null || !row.calculatedAt) continue; + if (new Date(row.calculatedAt) < cutoff) continue; + const price = Number(row.marketPrice); + if (price <= 0) continue; + if (!grouped[row.condition]) grouped[row.condition] = []; + grouped[row.condition].push(price); + } + + const result: Record = {}; + for (const [condition, prices] of Object.entries(grouped)) { + result[condition] = computeVolatility(prices); + } + return result; +} \ No newline at end of file