← Back
'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 };

📜 Git History

06783dafix: options-screener-v2 — 9 bug fixes, 5 strategy improvements, 2 infra enhancements3 months ago
163bb5dfeat: migrate to options-screener-v2 folder to isolate deployment4 months ago
Show last diff
Loading...