US = loadTable("C:/DolphinDB/Data/USstocks.csv")
//Step 1: Load the data file, clean the data by applying some filters on the data, and construct the momentum signal (past 12 months return skipping the most recent month) for each firm. Undefine table USstocks to release the large amount of memory it occupies. /*and HEXCD<4*/
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)
//Step 2: Generate the portfolios for the momentum strategy.
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)
// WtScheme 1:equal weight, 2:market value weight
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
//Step 3: Calculate the profit/loss for each stock in our portfolio in each of the subsequent holding period days. Close the position for a stock at the end of the holding period.
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]), dictIndexDate[dictDateIndex[tranche]+age]<=min(lastDays[PERMNO], endDate)
update pos set ret = dailyRet 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)
//Step 4: Calculate portfolio profit/loss, and plot the cumulative return of the momentum strategy over time.
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")