UNPKG

@atomic-utils/time

Version:
559 lines 23.7 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.getCsoLength = exports.isHalfMonth = exports.findNumCyclesInPastMaturityExists = exports.findCycleMaturityMonthsInPast = exports.extractCsoEventIdDateFromStr = exports.getEventIdType = exports.getCsoEventIdType = exports.getParamsFromManualEventId = exports.getParamsFromCsoTradeUnsplitEventId = exports.getParamsFromCsoTradeSplitEventId = exports.getParamsFromCsoEventId = exports.getManualEventId = exports.getCsoTradeUnsplitEventId = exports.getCsoTradeSplitEventId = exports.getCsoEventId = exports.getPreviousFriday = exports.getUpcomingFriday = exports.getCsoStartAndEndDate = exports.getCsoEvent = exports.getCsoEventDates = exports.getPreviousCycleMaturityDate = exports.getNextCycleMaturityDate = exports.getCurrentCycleMaturityDate = exports.getLastFridayInMonth = exports.STR_DATE_REGEX = exports.TRADING_OPEN_HALF_MONTH_LEN = exports.HALF_MONTH_ENTRY_CLOSED_LEN = exports.NEW_ENTRY_CLOSED_LEN = exports.NEW_ENTRY_OPEN_LEN = exports.ROLLOVER_OPEN_LEN = exports.DLC_ATTESTATION_LEN = exports.DLC_EXPIRY_LEN = void 0; exports.DLC_EXPIRY_LEN = 7; exports.DLC_ATTESTATION_LEN = 1; exports.ROLLOVER_OPEN_LEN = 24; exports.NEW_ENTRY_OPEN_LEN = 36; exports.NEW_ENTRY_CLOSED_LEN = 8; exports.HALF_MONTH_ENTRY_CLOSED_LEN = 6; exports.TRADING_OPEN_HALF_MONTH_LEN = 334; const deribit_1 = require("@atomic-utils/deribit"); const assert_1 = __importDefault(require("assert")); exports.STR_DATE_REGEX = /(\d{1,2})([A-Z]+)(\d{1,2})/; /** * getLastFridayInMonth * * Pass in year and month and return Date object with * last friday of the month * * @param {number} y Year in full format (i.e. 2022) * @param {number} m Month NOT 0-indexed (i.e. 1 => January) * @returns {Date} last friday of month */ const getLastFridayInMonth = (y, m) => { const lastDay = new Date(Date.UTC(y, m, 0, 8, 0, 0)); if (lastDay.getUTCDay() < 5) { lastDay.setDate(lastDay.getDate() - 7); } lastDay.setDate(lastDay.getDate() - (lastDay.getUTCDay() - 5)); return lastDay; }; exports.getLastFridayInMonth = getLastFridayInMonth; /** * getCurrentCycleMaturityDate * * @param {Date} t_ current time * @returns {Date} last friday in current cycle */ const getCurrentCycleMaturityDate = (t_) => { const t = new Date(t_.getTime()); // clone to avoid mutation let y = t.getUTCFullYear(); (0, assert_1.default)(y >= 2009, // since cycle maturity cannot be before Bitcoin was created `Invalid date provided ${t} with timestamp ${t.getTime()}, you may have used seconds instead of milliseconds`); (0, assert_1.default)(y <= 2100, // reasonable upper bound `Invalid date provided ${t} with timestamp ${t.getTime()}, you may have used microseconds instead of milliseconds`); let m = t.getUTCMonth() + 1; let lastFriday = (0, exports.getLastFridayInMonth)(y, m); if (t >= lastFriday) { t.setUTCDate(1); t.setUTCMonth(t.getUTCMonth() + 1); y = t.getUTCFullYear(); m = t.getUTCMonth() + 1; lastFriday = (0, exports.getLastFridayInMonth)(y, m); } return lastFriday; }; exports.getCurrentCycleMaturityDate = getCurrentCycleMaturityDate; /** * getNextCycleMaturityDate * * @param {Date} t_ current time * @returns {Date} last friday in next cycle */ const getNextCycleMaturityDate = (t_) => { const t = new Date(t_.getTime()); // clone to avoid mutation const currentLastFriday = (0, exports.getCurrentCycleMaturityDate)(t); currentLastFriday.setUTCSeconds(currentLastFriday.getUTCSeconds() + 1); const nextLastFriday = (0, exports.getCurrentCycleMaturityDate)(currentLastFriday); return nextLastFriday; }; exports.getNextCycleMaturityDate = getNextCycleMaturityDate; /** * getPreviousCycleMaturityDate * * @param {Date} t_ current time * @returns {Date} last friday in previous cycle */ const getPreviousCycleMaturityDate = (t_) => { const t = new Date(t_.getTime()); // clone to avoid mutation const currentLastFriday = (0, exports.getCurrentCycleMaturityDate)(t); currentLastFriday.setUTCMonth(currentLastFriday.getUTCMonth() - 1); currentLastFriday.setUTCDate(currentLastFriday.getDate() - 14); const previousLastFriday = (0, exports.getCurrentCycleMaturityDate)(currentLastFriday); return previousLastFriday; }; exports.getPreviousCycleMaturityDate = getPreviousCycleMaturityDate; const getCsoEventDates = (t_) => { const t = new Date(t_.getTime()); const upcomingDlcExpiry = (0, exports.getCurrentCycleMaturityDate)(t); const previousDlcExpiry = (0, exports.getPreviousCycleMaturityDate)(t); const dlcAttestation = new Date(previousDlcExpiry.getTime()); dlcAttestation.setUTCHours(dlcAttestation.getUTCHours() + exports.DLC_EXPIRY_LEN); const rolloverOpen = new Date(dlcAttestation.getTime()); rolloverOpen.setUTCHours(rolloverOpen.getUTCHours() + exports.DLC_ATTESTATION_LEN); const newEntryOpen = new Date(rolloverOpen.getTime()); newEntryOpen.setUTCHours(newEntryOpen.getUTCHours() + exports.ROLLOVER_OPEN_LEN); const newEntryClosed = new Date(newEntryOpen.getTime()); newEntryClosed.setUTCHours(newEntryClosed.getUTCHours() + exports.NEW_ENTRY_OPEN_LEN); const tradingOpen = new Date(newEntryClosed.getTime()); tradingOpen.setUTCHours(tradingOpen.getUTCHours() + exports.NEW_ENTRY_CLOSED_LEN); const tradingOpenHalfMonth = new Date(upcomingDlcExpiry.getTime()); tradingOpenHalfMonth.setUTCHours(tradingOpenHalfMonth.getUTCHours() - exports.TRADING_OPEN_HALF_MONTH_LEN); const halfMonthEntryClosed = new Date(tradingOpenHalfMonth.getTime()); halfMonthEntryClosed.setUTCHours(halfMonthEntryClosed.getUTCHours() - exports.HALF_MONTH_ENTRY_CLOSED_LEN); return { previousDlcExpiry, dlcAttestation, rolloverOpen, newEntryOpen, newEntryClosed, tradingOpen, halfMonthEntryClosed, tradingOpenHalfMonth, upcomingDlcExpiry, }; }; exports.getCsoEventDates = getCsoEventDates; /** * getCsoEvent * * @param {Date} t_ current time * @returns {CsoEvent} which cso event the date provided is within */ const getCsoEvent = (t_) => { const t = new Date(t_.getTime()); const { previousDlcExpiry, dlcAttestation, rolloverOpen, newEntryOpen, newEntryClosed, tradingOpen, halfMonthEntryClosed, tradingOpenHalfMonth, upcomingDlcExpiry, } = (0, exports.getCsoEventDates)(t); switch (true) { case t >= previousDlcExpiry && t < dlcAttestation: return 'dlcExpiry'; case t >= dlcAttestation && t < rolloverOpen: return 'dlcAttestation'; case t >= rolloverOpen && t < newEntryOpen: return 'rolloverOpen'; case t >= newEntryOpen && t < newEntryClosed: return 'newEntryOpen'; case t >= newEntryClosed && t < tradingOpen: return 'newEntryClosed'; case t >= tradingOpen && t < halfMonthEntryClosed: return 'tradingOpen'; case t >= halfMonthEntryClosed && t < tradingOpenHalfMonth: return 'halfMonthEntryClosed'; case t >= tradingOpenHalfMonth && t < upcomingDlcExpiry: return 'tradingOpenHalfMonth'; case t.getTime() === upcomingDlcExpiry.getTime(): return 'dlcExpiry'; } }; exports.getCsoEvent = getCsoEvent; /** * getCsoStartAndEndDate * * Pass in current time and get start and end date of event that user can enter into * immediately * * @param t_ current time * @returns {StartEndDates} start and end dates of the CSO event */ const getCsoStartAndEndDate = (t_, forceExtendedPeriod = false) => { const t = new Date(t_.getTime()); const csoEvent = (0, exports.getCsoEvent)(t); const { newEntryClosed, halfMonthEntryClosed, upcomingDlcExpiry } = (0, exports.getCsoEventDates)(t); const { upcomingDlcExpiry: followingDlcExpiry } = (0, exports.getCsoEventDates)(new Date(upcomingDlcExpiry.getTime() + 1)); if (csoEvent === 'halfMonthEntryClosed' || csoEvent === 'tradingOpenHalfMonth') { // Create full month for next month const nextT = new Date(upcomingDlcExpiry.getTime() + 1); const { newEntryClosed: nextNewEntryClosed, upcomingDlcExpiry: nextDlcExpiry, } = (0, exports.getCsoEventDates)(nextT); const { upcomingDlcExpiry: followingDlcExpiry } = (0, exports.getCsoEventDates)(new Date(nextDlcExpiry.getTime() + 1)); return { startDate: nextNewEntryClosed, endDate: forceExtendedPeriod ? followingDlcExpiry : nextDlcExpiry, }; } else if (csoEvent === 'newEntryClosed' || csoEvent === 'tradingOpen') { // Create half month event ID return { startDate: halfMonthEntryClosed, endDate: forceExtendedPeriod ? followingDlcExpiry : upcomingDlcExpiry, }; } else { // Create full month for current month return { startDate: newEntryClosed, endDate: forceExtendedPeriod ? followingDlcExpiry : upcomingDlcExpiry, }; } }; exports.getCsoStartAndEndDate = getCsoStartAndEndDate; /** * getUpcomingFriday * * From the current time, get the upcoming Friday. * If the current time is Friday and is after 8am UTC, then return the next week's Friday * * @param {Date} t_ current time * @returns */ const getUpcomingFriday = (t_) => { const t = new Date(t_.getTime()); let dayDelta = (5 - t.getUTCDay()) % 7; if (dayDelta === 0 && t.getUTCHours() >= 8) { dayDelta = 7; } t.setUTCDate(t.getUTCDate() + dayDelta); return new Date(t.setUTCHours(8, 0, 0, 0)); }; exports.getUpcomingFriday = getUpcomingFriday; /** * getPreviousFriday * * From the current time, get the previous Friday. * If the current time is Friday and is before 8am UTC, then return the previous week's Friday * * @param {Date} t_ current time * @returns */ const getPreviousFriday = (t_) => { const t = new Date(t_.getTime()); let dayDelta = (5 - t.getUTCDay() + 7) % 7; if (dayDelta === 0 && (t.getUTCHours() > 8 || (t.getUTCHours() === 8 && t.getUTCMinutes() === 0 && t.getUTCSeconds() > 0) || (t.getUTCHours() === 8 && t.getUTCMinutes() > 0))) { dayDelta = 7; } t.setUTCDate(t.getUTCDate() - (7 - dayDelta)); return new Date(t.setUTCHours(8, 0, 0, 0)); }; exports.getPreviousFriday = getPreviousFriday; /** * getCsoEventId * * Pass in Date and return event ID of announcement that user can enter into immediately * * @param {Date} t_ current time * @param {string} provider company or trader providing strategy * @param {string} strategyId unique identifier for strategy * @param {CsoPeriod} period i.e. monthly * @returns {string} event ID string i.e. atomic-call_spread_v1-monthly-27JUN22-29JUL22 */ const getCsoEventId = (t_, provider, strategyId, period, forceExtendedPeriod = false) => { const t = new Date(t_.getTime()); const { startDate, endDate } = (0, exports.getCsoStartAndEndDate)(t, forceExtendedPeriod); return [ provider, strategyId, period, (0, deribit_1.getStrDate)(startDate), (0, deribit_1.getStrDate)(endDate), ].join('-'); }; exports.getCsoEventId = getCsoEventId; /** * getCsoTradeSplitEventId * * This function generates an event ID for a split trade. * * @param {string} provider - The company or trader providing the strategy. * @param {string} strategyId - The unique identifier for the strategy. * @param {number} tradeIndex - The index of the trade. * @returns {string} - The event ID string in the format [provider]-[strategyId]-trade-[tradeIndex]. * i.e. atomic-oyster-trade-84 */ const getCsoTradeSplitEventId = (provider, strategyId, tradeIndex) => { return [provider, strategyId, 'trade', tradeIndex].join('-'); }; exports.getCsoTradeSplitEventId = getCsoTradeSplitEventId; /** * getCsoTradeUnsplitEventId * * This function generates an event ID for an unsplit trade. * * @param {Date} t_ - The current time. * @param {string} provider - The company or trader providing the strategy. * @param {string} strategyId - The unique identifier for the strategy. * @param {number} numTrades - The number of trades. * @returns {string} - The event ID string in the format [provider]-[strategyId]-[numTrades]-trades-[startDate]. * i.e. atomic-oyster-5-trades-1JAN24 */ const getCsoTradeUnsplitEventId = (t_, provider, strategyId, numTrades) => { const t = new Date(t_.getTime()); return [provider, strategyId, numTrades, 'trades', (0, deribit_1.getStrDate)(t)].join('-'); }; exports.getCsoTradeUnsplitEventId = getCsoTradeUnsplitEventId; const getManualEventId = (provider, source, maturity, symbol = 'BTC') => { const month = maturity .toLocaleString('default', { month: 'short' }) .toUpperCase() .split('.')[0]; const year = maturity.getFullYear().toString().slice(-2); const day = maturity.getDate().toString(); return `${provider}-${source}-${symbol}-${day}${month}${year}`; }; exports.getManualEventId = getManualEventId; /** * getStartAndEndDateFromCsoEventId * * Pass in eventId and return start and end date by checking if date is * equal to tradingOpen, tradingOpenHalfMonth or dlcExpiry, else just * output date inside cso params for eventId * * @param {string} eventId format [provider]-[strategyId]-[period]-[startDate]-[endDate] * @returns {CsoParams} provider, strategyId, period, and start and end dates */ const getParamsFromCsoEventId = (eventId) => { const eventParams = eventId.split('-'); if (eventParams.length !== 5) throw Error(`Invalid eventId provided: ${eventId}. Expected format [provider]-[strategyId]-[period]-[startDate]-[endDate]`); const [provider, strategyId, period, startDateStr, endDateStr] = eventParams; if (!exports.STR_DATE_REGEX.test(startDateStr) || !exports.STR_DATE_REGEX.test(endDateStr)) throw new Error(`Invalid start or end date provided. Start Date: ${startDateStr}. End Date: ${endDateStr}`); const startDate = (0, exports.extractCsoEventIdDateFromStr)(startDateStr); const endDate = (0, exports.extractCsoEventIdDateFromStr)(endDateStr); return { provider, strategyId, period, startDate, endDate, }; }; exports.getParamsFromCsoEventId = getParamsFromCsoEventId; const getParamsFromCsoTradeSplitEventId = (eventId) => { const eventParams = eventId.split('-'); if (eventParams.length !== 4) throw Error(`Invalid eventId provided: ${eventId}. Expected format [provider]-[strategyId]-[trade]-[tradeIndex]`); const [provider, strategyId, , tradeIndexStr] = eventParams; const tradeIndex = parseInt(tradeIndexStr); if (isNaN(tradeIndex)) throw Error(`Invalid tradeIndex provided: ${tradeIndexStr}. Expected integer`); return { provider, strategyId, tradeIndex, }; }; exports.getParamsFromCsoTradeSplitEventId = getParamsFromCsoTradeSplitEventId; const getParamsFromCsoTradeUnsplitEventId = (eventId) => { const eventParams = eventId.split('-'); if (eventParams.length !== 5) throw Error(`Invalid eventId provided: ${eventId}. Expected format [provider]-[strategyId]-[numTrades]-[trades]-[startDate]`); const [provider, strategyId, numTradesStr, , startDateStr] = eventParams; const numTrades = parseInt(numTradesStr); if (isNaN(numTrades)) throw Error(`Invalid numTrades provided: ${numTradesStr}. Expected integer`); if (!exports.STR_DATE_REGEX.test(startDateStr)) throw new Error(`Invalid start date provided: ${startDateStr}`); const startDate = (0, exports.extractCsoEventIdDateFromStr)(startDateStr); return { provider, strategyId, numTrades, startDate, }; }; exports.getParamsFromCsoTradeUnsplitEventId = getParamsFromCsoTradeUnsplitEventId; /** * getParamsFromManualEventId * * Pass in eventId and return provider, source, maturity, and symbol * * @param {string} eventId format [provider]-[source]-[symbol]-[maturity] * @returns {ManualEventParams} provider, source, maturity, and symbol */ const getParamsFromManualEventId = (eventId) => { const eventParams = eventId.split('-'); if (eventParams.length !== 4) throw Error(`Invalid eventId provided: ${eventId}. Expected format [provider]-[source]-[symbol]-[maturity]`); const [provider, source, symbol, maturityStr] = eventParams; if (!exports.STR_DATE_REGEX.test(maturityStr)) throw new Error(`Invalid maturity date provided: ${maturityStr}`); const maturity = (0, exports.extractCsoEventIdDateFromStr)(maturityStr); return { provider, source, symbol, maturity, }; }; exports.getParamsFromManualEventId = getParamsFromManualEventId; const getCsoEventIdType = (eventId) => { const eventParams = eventId.split('-'); // Check the length and specific parts of the split eventId to determine its type switch (eventParams.length) { case 5: // If the eventId splits into 5 parts, it could be 'period' or 'unsplit' // Check the fourth part to differentiate return eventParams[3] === 'trades' ? 'unsplit' : 'period'; case 4: // If the eventId splits into 4 parts, it's a 'split' return 'split'; default: // If the eventId doesn't match the above cases, throw an error throw new Error(`Invalid eventId provided: ${eventId}`); } }; exports.getCsoEventIdType = getCsoEventIdType; const getEventIdType = (eventId) => { const eventParams = eventId.split('-'); // Check if the eventId is of type 'manual' if (eventParams.length === 4 && eventParams[2] !== 'trade') { return 'manual'; } // If not 'manual', use the existing function to determine the type return (0, exports.getCsoEventIdType)(eventId); }; exports.getEventIdType = getEventIdType; /** * extractCsoEventIdDateFromStr * * Get Date from string date while checking if date matches tradingOpen, * tradingOpenHalfMonth or dlcExpiry * * @param {string} dateStr string date in format [day][month][year] i.e. 21AUG22 * @returns {Date} */ const extractCsoEventIdDateFromStr = (dateStr) => { const [, day, month, year] = dateStr.match(exports.STR_DATE_REGEX); // Create a more reliable date parsing that works across all JS environments // Map month abbreviations to month numbers (0-indexed) const monthMap = { JAN: 0, FEB: 1, MAR: 2, APR: 3, MAY: 4, JUN: 5, JUL: 6, AUG: 7, SEP: 8, OCT: 9, NOV: 10, DEC: 11, }; const monthNum = monthMap[month]; if (monthNum === undefined) { throw new Error(`Invalid month abbreviation: ${month}`); } // Convert 2-digit year to 4-digit year (assuming 20xx for years 00-99) const fullYear = parseInt(year) < 50 ? 2000 + parseInt(year) : 1900 + parseInt(year); const dayNum = parseInt(day); // Create date using UTC constructor for consistency const date = new Date(Date.UTC(fullYear, monthNum, dayNum, 12, 0, 0, 0)); // Validate the date was created successfully if (isNaN(date.getTime())) { throw new Error(`Invalid date created from: ${dateStr}`); } const csoEvent = (0, exports.getCsoEvent)(date); const { previousDlcExpiry, newEntryClosed, tradingOpen, tradingOpenHalfMonth, upcomingDlcExpiry, } = (0, exports.getCsoEventDates)(date); if (csoEvent === 'tradingOpen') { if (date.getUTCDate() === tradingOpen.getUTCDate()) return newEntryClosed; } else if (csoEvent === 'tradingOpenHalfMonth') { if (date.getUTCDate() === tradingOpenHalfMonth.getUTCDate()) return tradingOpenHalfMonth; } else if (csoEvent === 'dlcExpiry') { if (date.getUTCDate() === upcomingDlcExpiry.getUTCDate()) { return upcomingDlcExpiry; } else if (date.getUTCDate() === previousDlcExpiry.getUTCDate()) { return previousDlcExpiry; } } else if (csoEvent === 'newEntryClosed') { return newEntryClosed; } return date; }; exports.extractCsoEventIdDateFromStr = extractCsoEventIdDateFromStr; /** * findCycleMaturityMonthsInPast * * Enter number of cycles to look in past and get cycle maturity * i.e. passing in numMonths 3 will get the cycle maturity 3 months ago * * @param {Date} t_ current time * @param {number} numMonths number of months to go back * @returns {Date} */ const findCycleMaturityMonthsInPast = (t_, numMonths) => { let t = new Date(t_.getTime()); if (numMonths === 0) throw Error('numMonths must be at least 1'); for (let i = 0; i < numMonths; i++) { t = (0, exports.getPreviousCycleMaturityDate)(new Date(t.getTime() - 1)); } return t; }; exports.findCycleMaturityMonthsInPast = findCycleMaturityMonthsInPast; /** * findNumCyclesInPastMaturityExists * * Enter previousExpiry and find out how many months ago this expiry was * * @param {Date} t_ current time * @param {Date} previousExpiry previous cycle expiry * @returns {number} */ const findNumCyclesInPastMaturityExists = (t_, previousExpiry_, maxTries = 1000) => { let t = new Date(t_.getTime()); if (previousExpiry_.getTime() >= t.getTime()) throw Error('Previous Expiry should be less than current date'); if ((0, exports.getCsoEvent)(previousExpiry_) !== 'dlcExpiry') throw Error('Previous Expiry should be in time period dlcExpiry'); const { previousDlcExpiry } = (0, exports.getCsoEventDates)(previousExpiry_); const previousExpiry = previousDlcExpiry; if (t.getTime() === previousExpiry.getTime()) return 0; for (let i = 0; i < maxTries; i++) { t = (0, exports.getPreviousCycleMaturityDate)(new Date(t.getTime() - 1)); if (t.getTime() === previousExpiry.getTime()) return i + 1; } throw Error(`Could not find cycle maturity in the past after checking ${maxTries} months`); }; exports.findNumCyclesInPastMaturityExists = findNumCyclesInPastMaturityExists; /** * isHalfMonth * * Determine if the current cycle is a half month cycle * @param {string} eventId current time * @returns {boolean} whether the current cycle is a half month */ const isHalfMonth = (eventId) => { const startDate = (0, exports.getParamsFromCsoEventId)(eventId).startDate; return (startDate.getTime() === (0, exports.getCsoEventDates)(startDate).tradingOpenHalfMonth.getTime() || startDate.getTime() === (0, exports.getCsoEventDates)(startDate).halfMonthEntryClosed.getTime()); }; exports.isHalfMonth = isHalfMonth; const getCsoLength = (eventId) => { const { startDate, endDate } = (0, exports.getParamsFromCsoEventId)(eventId); const containsHalfMonth = (0, exports.isHalfMonth)(eventId); // Get the difference in days const diffInDays = Math.floor((endDate.getTime() - startDate.getTime()) / (1000 * 60 * 60 * 24)); if (containsHalfMonth) { if (diffInDays > 30) return 'one-and-a-half-months'; else return 'half-month'; } else { if (diffInDays > 45) return 'two-months'; else return 'full-month'; } }; exports.getCsoLength = getCsoLength; //# sourceMappingURL=cso.js.map