获取期权、期货的合约月份和交割日

159 阅读3分钟

背景

最近在研究期权期货,有个 2 个关键指标「合约月份」和「交割日期」很常用,但是难获取。它们的计算逻辑其实挺清楚的,但是就是没找到现成的库(如果有知道这方面库的同学请留言推荐),所以自己写了一个函数。

正文

规则

我个人关注的主要有 3 个期权/期货产品:ETF 期权、股指期权、股指期货。他们的规则可以在 中国金融期货交易所网站 上查得到。规则如下:

产品合约月份交割日期
ETF 期权当月、下月及随后两个季月合约到期月份的第四个周三,遇国家法定假日顺延
股指期权当月、下2个月及随后3个季月合约到期月份的第三个周五,遇国家法定假日顺延
股指期货当月、下月及随后两个季月合约到期月份的第三个周五,遇国家法定假日顺延

获取某月第 n 个星期 m

先来解决这个问题,直接上代码:

// 获取某几月第n个星期day
export const getNthDayOfMonths = (yearMonths: string[], n = 3, day = 5) =>
  yearMonths.map((ym) => {
    const firstDayOfMonth = moment(ym);
    let diff = day - 7 - firstDayOfMonth.day();
    if (firstDayOfMonth.day() > day) {
      diff += 7;
    }
    return firstDayOfMonth.add(7 * n + diff, 'days').format('YYYY-MM-DD');
  });

关键点:

  1. 入参要支持多个月份的计算;
  2. 判断当月第一天是否在星期 m 之后,分情况处理要加的天数 diff;
  3. 此方法没有考虑法定节假日,能加是能加,成本稍微有点大。有兴趣的同学可以移步 《如何判断当天是否是交易日(工作日)》 来查看判断节假日的逻辑。

获取合约月份

有了上面的方法,好多地方就好写了,先上代码:

export enum TradeMonthType {
  ETFOp, // ETF 期权
  IndexOp, // 股指期权
  IndexFeat, // 股指期货
}
// ETF 期权、股指期权、股指期货交易月份及交割日
export const getDealMonthsAndDates = (
  type = TradeMonthType.ETFOp,
  startDate?: string
) => {
  const currDate = moment(startDate);
  // 初始化参数
  let nextMonthCount = 1;
  let nextQuaterCount = 2;
  let dealDateWeek = 4;
  let dealDateDay = 3;
  // 根据 type 重置参数
  if (type === TradeMonthType.IndexOp) {
    nextMonthCount = 2;
    nextQuaterCount = 3;
    dealDateWeek = 3;
    dealDateDay = 5;
  }
  if (type === TradeMonthType.IndexFeat) {
    dealDateWeek = 3;
    dealDateDay = 5;
  }
  // 判断起始月份
  const [currMonthDealDate] = getNthDayOfMonths(
    [currDate.format('YYYY-MM')],
    dealDateWeek,
    dealDateDay
  );
  if (moment().isSameOrAfter(moment(currMonthDealDate).startOf('D'))) {
    currDate.add(1, 'M');
  }
  // 处理月合约
  const monthSet = new Set([currDate.format('YYYYMM')]);
  for (let i = 0; i < nextMonthCount; i++) {
    monthSet.add(currDate.add(1, 'M').format('YYYYMM'));
  }
  // 处理季合约
  let currQuarterEnd = currDate.clone().endOf('quarter');
  if (currDate.month() !== currQuarterEnd.month()) {
    currQuarterEnd.add(-1, 'Q');
  }
  for (let i = 0; i < nextQuaterCount; i++) {
    monthSet.add(currQuarterEnd.add(1, 'Q').format('YYYYMM'));
  }
  const months = Array.from(monthSet);
  return {
    type,
    months,
    dealDates: getNthDayOfMonths(months, dealDateWeek, dealDateDay),
  };
};

关键点:

  1. 肯定要根据规则分 type 判断,处理一些初始化的参数,如:第 dealDateWeek 个星期 dealDateDay,下 nextMonthCount 个月和 nextQuaterCount 个季月;
  2. 要判断当前时间是否已经过了交割日,来确定起始月份是当月还是下月;
  3. 还要对季月的逻辑进行额外判断,最后保险起见去个重。

总结

这个功能比较常用,在一些非严肃的场景还是可以用一用的,比如查看数据什么的。但是在一些严肃场景,比如实盘交易,最好还是获取软件或交易所提供的 API 数据。

发到这里也是为了记录下,省的这代码后面找不到了。