← Back
const { Router } = require('express');
const { checkApiKey } = require('../middleware/auth');
const { cache } = require('../services/cache');
const { fetchSpotPrices } = require('../services/binance');
const { findUnusualVolume } = require('../analysis/unusualVolume');
const { findTopMovers } = require('../analysis/topMovers');
const { calculateAllMaxPain } = require('../analysis/maxPain');
const { getAtmIv, calculateIvRankAndPercentile } = require('../analysis/ivAnalysis');
const { analyzeAllPCRatios } = require('../analysis/putCallRatio');
const { analyzeSkew } = require('../analysis/ivSkew');
const { findGammaPlays } = require('../analysis/gammaPlay');
const { analyzeOiConcentration } = require('../analysis/oiConcentration');
const { getAtmIvHistory } = require('../services/ivHistory');
const { mergeGreeksIntoOptions } = require('../utils/mergeGreeks');

const router = Router();

function mergeGreeks(optionsData) {
  return mergeGreeksIntoOptions(optionsData, cache.greeks);
}

router.get('/api/signals/unusual-volume', checkApiKey, async (req, res) => {
  if (!cache.options) {
    return res.status(503).json({ error: 'Data not ready' });
  }
  const spotPrices = await fetchSpotPrices();
  const data = mergeGreeks(cache.options);
  const result = findUnusualVolume(data, spotPrices, {
    underlying: req.query.underlying || null,
    limit: parseInt(req.query.limit) || 30,
  });
  res.json({ success: true, lastUpdate: cache.lastUpdate, ...result });
});

router.get('/api/signals/iv-analysis', checkApiKey, async (req, res) => {
  if (!cache.options) {
    return res.status(503).json({ error: 'Data not ready' });
  }
  const days = parseInt(req.query.days) || 30;
  const spotPrices = await fetchSpotPrices();
  const data = mergeGreeks(cache.options);
  const results = {};
  for (const [asset, spot] of Object.entries(spotPrices)) {
    const atm = getAtmIv(data, asset, spot);
    if (!atm) continue;
    const history = await getAtmIvHistory(asset, days);
    const ranked = calculateIvRankAndPercentile(atm.iv, history);
    results[asset] = {
      ...atm,
      spotPrice: spot,
      ...ranked,
      historyDays: days,
      historyPoints: history.length,
      rankSource: history.length >= 10 ? 'db' : 'insufficient_history',
    };
  }
  res.json({ success: true, lastUpdate: cache.lastUpdate, data: results });
});

router.get('/api/signals/max-pain', checkApiKey, async (req, res) => {
  if (!cache.options) {
    return res.status(503).json({ error: 'Data not ready' });
  }
  const spotPrices = await fetchSpotPrices();
  const data = mergeGreeks(cache.options);
  const result = calculateAllMaxPain(data, spotPrices);
  res.json({ success: true, lastUpdate: cache.lastUpdate, count: result.length, data: result });
});

router.get('/api/signals/oi-concentration', checkApiKey, async (req, res) => {
  if (!cache.options) {
    return res.status(503).json({ error: 'Data not ready' });
  }
  const data = mergeGreeks(cache.options);
  const underlying = req.query.underlying ? req.query.underlying.toUpperCase() : null;
  const result = analyzeOiConcentration(data, {
    underlying,
    limit: parseInt(req.query.limit) || 20,
  });
  res.json({
    success: true,
    lastUpdate: cache.lastUpdate,
    underlying: underlying || 'ALL',
    count: result.length,
    data: result,
  });
});

router.get('/api/signals/put-call-ratio', checkApiKey, (req, res) => {
  if (!cache.options) {
    return res.status(503).json({ error: 'Data not ready' });
  }
  const data = mergeGreeks(cache.options);
  const underlying = req.query.underlying ? req.query.underlying.toUpperCase() : null;
  const result = analyzeAllPCRatios(data, underlying);
  res.json({ success: true, lastUpdate: cache.lastUpdate, ...result });
});

router.get('/api/signals/iv-skew', checkApiKey, async (req, res) => {
  if (!cache.options) {
    return res.status(503).json({ error: 'Data not ready' });
  }
  const spotPrices = await fetchSpotPrices();
  const data = mergeGreeks(cache.options);
  const results = {};
  for (const [asset, spot] of Object.entries(spotPrices)) {
    results[asset] = analyzeSkew(data, asset, spot);
  }
  res.json({ success: true, lastUpdate: cache.lastUpdate, data: results });
});

router.get('/api/signals/gamma-play', checkApiKey, async (req, res) => {
  if (!cache.options) {
    return res.status(503).json({ error: 'Data not ready' });
  }
  const spotPrices = await fetchSpotPrices();
  const data = mergeGreeks(cache.options);
  const result = findGammaPlays(data, spotPrices, {
    limit: parseInt(req.query.limit) || 20,
  });
  res.json({ success: true, lastUpdate: cache.lastUpdate, count: result.length, data: result });
});

module.exports = router;

📜 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...