← Назад'use strict';
const db = require('./db');
const { getAtmIv, calculateIvRankAndPercentile } = require('../analysis/ivAnalysis');
const logger = require('../utils/logger');
const MIN_HISTORY_POINTS = 10; // need at least this many records before using real rank
// Save ATM IV snapshot for each underlying to AtmIvHistory table
async function saveAtmIvSnapshots(options, spotPrices) {
for (const [underlying, spot] of Object.entries(spotPrices)) {
const atm = getAtmIv(options, underlying, spot);
if (!atm || atm.iv === 0) continue;
try {
await db.atmIvHistory.create({
data: {
underlying,
atmIv: atm.iv,
spotPrice: spot,
},
});
} catch (err) {
logger.error(`ivHistory save error (${underlying}): ${err.message}`);
}
}
}
// Return array of atmIv floats for the last N days
async function getAtmIvHistory(underlying, days = 30) {
const since = new Date(Date.now() - days * 24 * 60 * 60 * 1000);
try {
const records = await db.atmIvHistory.findMany({
where: { underlying, createdAt: { gte: since } },
orderBy: { createdAt: 'asc' },
select: { atmIv: true },
});
return records.map(r => r.atmIv);
} catch (err) {
logger.error(`ivHistory query error (${underlying}): ${err.message}`);
return [];
}
}
// Full IV context with real historical rank.
// Returns null if not enough history — caller should fall back to pseudo-rank.
async function getRealIvContext(underlying, currentAtmIv, days = 30) {
const history = await getAtmIvHistory(underlying, days);
if (history.length < MIN_HISTORY_POINTS) return null;
const ranked = calculateIvRankAndPercentile(currentAtmIv, history);
return {
atmIv: currentAtmIv,
ivRank: ranked.ivRank,
ivPercentile: ranked.ivPercentile,
ivMin: ranked.minIv,
ivMax: ranked.maxIv,
ivAvg: ranked.avgIv,
status: ranked.status,
dataPoints: history.length,
historyDays: days,
source: 'db',
};
}
// Build real IV contexts for BTC and ETH to pass into strategy analyzer
async function buildIvContexts(options, spotPrices) {
const contexts = {};
for (const [underlying, spot] of Object.entries(spotPrices)) {
const atm = getAtmIv(options, underlying, spot);
if (!atm || atm.iv === 0) continue;
const ctx = await getRealIvContext(underlying, atm.iv);
if (ctx) {
contexts[underlying] = { ...ctx, atmSymbol: atm.symbol, atmStrike: atm.strike };
}
}
return contexts;
}
module.exports = { saveAtmIvSnapshots, getAtmIvHistory, getRealIvContext, buildIvContexts };