Example 11. 动量交易策略




这一节讲述关于动量策略的一个测试实例。动量策略是最著名的定量长短期股票策略之一。自从Jegadeesh和Titman(1993)首次提出这个概念以来,它已广泛出现在学术研究和销售方面的著作中。投资者在动量策略中相信,个股中,过去的赢家将超越过去的输家。


最常用的动量因素是股票除去最近一个月在过去12个月的收益。 在学术出版物中,动量策略通常是一个月调整一次且持有期也是一个月。在本例中,我们每天重新平衡我们的投资组合的1 / 21,并持有新份额21天。为简单起见,我们不考虑交易成本。


步骤 1: 加载股票交易数据, 对数据进行清洗和筛选, 然后为每个公司股票构建一个过去12个月忽略最近一个月的动量信号。最后,通过 Undefine US 命令来释放大量内存占用。



US = loadTable("C:/DolphinDB/Data/USstocks.csv")


def loadPriceData(inData){

       USstocks = select PERMNO, date, abs(PRC) as PRC, VOL, RET, SHROUT*abs(PRC) as MV from inData where weekday(date) between 1:5, isValid(PRC), isValid(VOL) order by PERMNO, date

       USstocks = select PERMNO, date, PRC, VOL, RET, MV, cumprod(1+RET) as cumretIndex from USstocks context by PERMNO

       return select PERMNO, date, PRC, VOL, RET, MV, move(cumretIndex,21)\move(cumretIndex,252)-1 as signal from USstocks context by PERMNO

}

priceData = loadPriceData(US)



步骤 2: 为动量策略生成投资组合。


首先,选择满足以下条件的流通股:动量信号值无缺失、当日是正交易量、市值超过1亿美金、以及 每股价格超过5美元。



def genTradables(indata){

       return select date, PERMNO, MV, signal from indata where PRC>5, MV>100000, VOL>0, isValid(signal) order by date

}

tradables = genTradables(priceData)



其次,根据动量信号,制定10组可交易股票。只保留2个最极端的群体(赢家和输家)。假设我们总是想在21天内,每天多头1美元和空头$1,所以我们每天在赢家组多头$1/21,在输家组每天空头$1/21。在每组中,我们可以使用等权重或值权重, 来计算投资组合形成日期上每个股票的权重。



// WtScheme=1表示等权重 ,WtScheme=2表示值权重

def formPortfolio(startDate, endDate, tradables, holdingDays, groups, WtScheme){

       ports = select date, PERMNO, MV, rank(signal,,groups) as rank, count(PERMNO) as symCount, 0.0 as wt from tradables where date between startDate:endDate context by date having count(PERMNO)>=100

       if (WtScheme==1){

               update ports set wt = -1.0\count(PERMNO)\holdingDays where rank=0 context by date

               update ports set wt = 1.0\count(PERMNO)\holdingDays where rank=groups-1 context by date

       }

       else if (WtScheme==2){

               update ports set wt = -MV\sum(MV)\holdingDays where rank=0 context by date

               update ports set wt = MV\sum(MV)\holdingDays where rank=groups-1 context by date

       }

       return select PERMNO, date as tranche, wt from ports where wt != 0 order by PERMNO, date

}

startDate=1996.01.01

endDate=2017.01.01

holdingDays=21

groups=10

ports = formPortfolio(startDate, endDate, tradables, holdingDays, groups, 2)

dailyRtn = select date, PERMNO, RET as dailyRet from priceData where date between startDate:endDate



步骤3:计算我们的投资组合中的每支股票随后的21天利润/损失。投资组合形成日后21天关闭股票。



def calcStockPnL(ports, dailyRtn, holdingDays, endDate, lastDays){

       ages = table(1..holdingDays as age)

       dates = sort distinct ports.tranche

           dictDateIndex = dict(dates, 1..dates.size())

           dictIndexDate = dict(1..dates.size(), dates)

       pos = select dictIndexDate[dictDateIndex[tranche]+age] as date, PERMNO, tranche, age, take(0.0,size age) as ret, wt as expr, take(0.0,size age) as pnl from cj(ports,ages) where isValid(dictIndexDate[dictDateIndex[tranche]+age]) and dictIndexDate[dictDateIndex[tranche]+age]<=min(lastDays[PERMNO], endDate)

       update pos set ret = RET from ej(pos, dailyRtn,`date`PERMNO)

       update pos set expr = expr*cumprod(1+ret) from pos context by PERMNO, tranche

       update pos set pnl = expr*ret/(1+ret)

       return pos

}


lastDaysTable = select max(date) as date from priceData group by PERMNO

lastDays = dict(lastDaysTable.PERMNO, lastDaysTable.date)

undef(`priceData, VAR)

stockPnL = calcStockPnL(ports, dailyRtn, holdingDays, endDate, lastDays)



步骤 4:计算投资组合的利润/损失,并绘制随着时间推移的动量策略的累积回报。



portPnL = select sum(pnl) as pnl from stockPnL group by date

portPnL = select * from portPnL order by date;

plot(cumsum(portPnL.pnl) as cumulativeReturn,portPnL.date, "Cumulative Returns of the Momentum Strategy")





Download source code here.