
+
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