Below is the solution I've come up with. It's no doubt neither efficient nor idiomatic (and the last line doesn't work), so I'd welcome any suggested improvements.
Thanks,
Matt.
/ Returns OHLC bars for front-month future contract beginning with prefix in range (start;end).
/ Data is back-adjusted for contract rolls, which occur at the end of the day before the far-month
/ contract becomes more liquid than the near-month.
/ Roll level is calculated as the median price difference across the last n bars
contFuture:{[prefix;start;end;n;tbl]
/ Returns the median difference in open/close prices for the last n bars before d where both s1 & s2 traded
medDiff:{[s1;s2;d;n;t]
/ Table of last n bars that match between the two symbols
lt:(neg n)#ej[`date`time;select date,time,o1:open,c1:close from t where date<d,sym=s1,size>0;select date,time,o2:open,c2:close from t where date<d,sym=s2,size>0];
:med (lt[`o1]-lt[`o2]),(lt[`c1]-lt[`c2]);
};
/ Data for candidate symbols in global var (so we can pass `t to medDiff)
t::select from tbl where date>=start,date<=end,sym like (string[prefix],"*");
/ Gets symbol with the greatest volume on each day
symbolByDate:select sym:first sym where size=max size by date from (select sum size by date, sym from t);
/ Get table of new sym, old sym, and rollover date
rollTable:select symAfter:sym, symBefore:prev sym, date from (`date xasc select first date by sym from symbolByDate);
/ Add columns for medDiff function (yuck!)
rollTable[`n`t]:(n;`t);
/ Calc cumulative diff column; replace null with 0
rollTable[`cumDiff]:0^next reverse (0+\(reverse 0^medDiff .' flip value flip rollTable));
/ Remove unneeded columns and rename symAfter
rollTable:`sym xcol `symBefore`n`t _ rollTable;
/ Add null cumDiff entries on the rollover dates for each symbol we've rolled off
rollTable,:1_select prev sym, date, cumDiff:0n from rollTable;
/ Select bars from each contract based on the rollTable dates; join with cumDiff
rawBars:select from aj[`sym`date;t;$[not count rollTable;:t;rollTable]] where cumDiff <> 0n;
/ Apply offset
:select date, sym, time, open:open+cumDiff, high:high+cumDiff, low:low+cumDiff, close:close+cumDiff, size from rawBars;
/ Delete global t var. Doesn't work?
delete t from `..;
}