← Back
const axios = require('axios');
const config = require('../config');
const logger = require('../utils/logger');

async function fetchOptions() {
  try {
    const resp = await axios.get(config.binance.tickerUrl);
    return resp.data;
  } catch (err) {
    logger.error(`Binance ticker error: ${err.message}`);
    return null;
  }
}

async function fetchGreeks() {
  try {
    const resp = await axios.get(config.binance.markUrl);
    return resp.data;
  } catch (err) {
    logger.error(`Binance greeks error: ${err.message}`);
    return null;
  }
}

async function fetchSpotPrices() {
  try {
    const resp = await axios.get('https://api.binance.com/api/v3/ticker/price', {
      params: { symbols: '["BTCUSDT","ETHUSDT","SOLUSDT","DOGEUSDT","XRPUSDT","BNBUSDT"]' },
    });
    const prices = {};
    for (const item of resp.data) {
      if (item.symbol === 'BTCUSDT') prices.BTC = parseFloat(item.price);
      if (item.symbol === 'ETHUSDT') prices.ETH = parseFloat(item.price);
      if (item.symbol === 'SOLUSDT') prices.SOL = parseFloat(item.price);
      if (item.symbol === 'DOGEUSDT') prices.DOGE = parseFloat(item.price);
      if (item.symbol === 'XRPUSDT') prices.XRP = parseFloat(item.price);
      if (item.symbol === 'BNBUSDT') prices.BNB = parseFloat(item.price);
    }
    return prices;
  } catch (err) {
    logger.error(`Binance spot price error: ${err.message}`);
    return {};
  }
}

/**
 * Fetch open interest from /eapi/v1/openInterest.
 * Requires both underlyingAsset AND expiration params.
 * @param {Array} options - ticker data to extract expiry dates from
 */
async function fetchOpenInterest(options) {
  const oiMap = {}; // symbol → sumOpenInterest
  if (!options || !options.length) return oiMap;

  // Extract unique underlying+expiry combos from ticker symbols
  // Symbol format: BTC-260412-69500-C
  const combos = new Map(); // "BTC|260412" → true
  for (const o of options) {
    const parts = o.symbol.split('-');
    if (parts.length >= 3) {
      combos.set(`${parts[0]}|${parts[1]}`, true);
    }
  }

  // Fetch OI for each underlying+expiry combo (parallel per underlying)
  const tasks = [];
  for (const key of combos.keys()) {
    const [asset, expiration] = key.split('|');
    tasks.push(
      axios.get('https://eapi.binance.com/eapi/v1/openInterest', {
        params: { underlyingAsset: asset, expiration },
        timeout: 5000,
      }).then(resp => {
        if (Array.isArray(resp.data)) {
          for (const item of resp.data) {
            oiMap[item.symbol] = parseFloat(item.sumOpenInterest || 0);
          }
        }
      }).catch(err => {
        // -6010 = no data for this combo, skip silently
        const code = err.response?.data?.code;
        if (code !== -6010) {
          logger.error(`OI fetch error (${asset}/${expiration}): ${err.message}`);
        }
      })
    );
  }

  await Promise.all(tasks);
  logger.info(`OI fetched: ${Object.keys(oiMap).length} symbols from ${combos.size} expiry combos`);
  return oiMap;
}

/**
 * Fetch contract unit multipliers from exchangeInfo.
 * XRP=100, DOGE=1000, BTC/ETH/SOL/BNB=1.
 * Premium in API is per-contract: pricePerUnit = lastPrice / unit.
 * Returns { BTC: 1, ETH: 1, SOL: 1, BNB: 1, XRP: 100, DOGE: 1000, ... }
 */
async function fetchContractUnits() {
  try {
    const resp = await axios.get('https://eapi.binance.com/eapi/v1/exchangeInfo');
    const units = {};
    for (const sym of (resp.data.optionSymbols || [])) {
      const underlying = sym.underlying?.replace('USDT', '');
      if (underlying && !units[underlying]) {
        units[underlying] = parseInt(sym.unit) || 1;
      }
    }
    logger.info(`[BINANCE] Contract units loaded: ${JSON.stringify(units)}`);
    return units;
  } catch (err) {
    logger.error(`[BINANCE] Failed to load contract units: ${err.message}`);
    return { BTC: 1, ETH: 1, SOL: 1, BNB: 1, XRP: 100, DOGE: 1000 }; // fallback
  }
}

module.exports = { fetchOptions, fetchGreeks, fetchSpotPrices, fetchOpenInterest, fetchContractUnits };

📜 Git History

36ce19afix: normalize option premium by contract unit (XRP=100, DOGE=1000)3 months ago
612398cfeat: signal reliability overhaul — OI data, liquidity gate, realistic PnL tracking3 months ago
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...