← Назадconst cache = {
options: null,
greeks: null,
spotPrices: null,
lastUpdate: null,
contractUnits: null, // { BTC: 1, ETH: 1, XRP: 100, DOGE: 1000, ... }
};
/**
* Get contract unit for an underlying (1 contract = N units of the asset).
* XRP=100, DOGE=1000, others=1. Premium from API is per-contract.
* pricePerUnit = lastPrice / getContractUnit(underlying)
*/
function getContractUnit(underlying) {
return cache.contractUnits?.[underlying] || 1;
}
function parseSymbol(symbol) {
const parts = symbol.split('-');
if (parts.length !== 4) return null;
return {
underlying: parts[0],
expiry: parts[1],
strike: parts[2],
type: parts[3] === 'C' ? 'CALL' : 'PUT',
};
}
function enrichWithGreeks(options) {
return options.map(o => {
const g = cache.greeks ? cache.greeks[o.symbol] : null;
return {
...o,
greeks: g ? {
delta: g.delta,
gamma: g.gamma,
theta: g.theta,
vega: g.vega,
markIV: g.markIV,
} : null,
};
});
}
function applyFilters(options, query) {
let result = options;
if (query.underlying) {
const u = query.underlying.toUpperCase();
result = result.filter(o => o.symbol.startsWith(u + '-'));
}
if (query.expiry) {
result = result.filter(o => o.symbol.includes('-' + query.expiry + '-'));
}
if (query.type) {
const t = query.type.toUpperCase();
const suffix = t === 'CALL' ? '-C' : '-P';
result = result.filter(o => o.symbol.endsWith(suffix));
}
if (query.minStrike) {
const min = parseFloat(query.minStrike);
result = result.filter(o => {
const p = parseSymbol(o.symbol);
return p && parseFloat(p.strike) >= min;
});
}
if (query.maxStrike) {
const max = parseFloat(query.maxStrike);
result = result.filter(o => {
const p = parseSymbol(o.symbol);
return p && parseFloat(p.strike) <= max;
});
}
if (query.minVolume) {
const min = parseFloat(query.minVolume);
result = result.filter(o => parseFloat(o.volume || 0) >= min);
}
if (query.minDelta) {
const min = parseFloat(query.minDelta);
result = result.filter(o => {
if (!cache.greeks) return false;
const g = cache.greeks[o.symbol];
return g && parseFloat(g.delta) >= min;
});
}
if (query.maxDelta) {
const max = parseFloat(query.maxDelta);
result = result.filter(o => {
if (!cache.greeks) return false;
const g = cache.greeks[o.symbol];
return g && parseFloat(g.delta) <= max;
});
}
if (query.minGamma) {
const min = parseFloat(query.minGamma);
result = result.filter(o => {
if (!cache.greeks) return false;
const g = cache.greeks[o.symbol];
return g && parseFloat(g.gamma) >= min;
});
}
if (query.minVega) {
const min = parseFloat(query.minVega);
result = result.filter(o => {
if (!cache.greeks) return false;
const g = cache.greeks[o.symbol];
return g && parseFloat(g.vega) >= min;
});
}
if (query.minIV) {
const min = parseFloat(query.minIV);
result = result.filter(o => {
if (!cache.greeks) return false;
const g = cache.greeks[o.symbol];
return g && parseFloat(g.markIV) >= min;
});
}
if (query.maxIV) {
const max = parseFloat(query.maxIV);
result = result.filter(o => {
if (!cache.greeks) return false;
const g = cache.greeks[o.symbol];
return g && parseFloat(g.markIV) <= max;
});
}
return result;
}
module.exports = { cache, parseSymbol, enrichWithGreeks, applyFilters, getContractUnit };