Generally any pipelines are defined in the 'initialize' method. This simply sets up the columns one wants in the returned dataframe when the pipeline is run. It's typically done in the 'initialize' method because it only needs to be done once and before the pipeline is actually run. The key point is that this simply defines the pipeline.
The statement "The price data can be accessed only through the 'data' object" isn't entirely true. The same price data (and more) can be accessed through pipelines. The exception is minute and current days data. Pipelines only have access to daily data and the latest available data is the previous trading day. So, unless one needs minute data and/or current day data, then it's all available using pipelines.
Below is a simple custom factor to get previous values of an input factor. The window length determines how many trading days to 'look back'.
# Create a custom factor which takes as an input another factor
# The output will be that factors value n days ago
# n is set by setting the window length equal to n+1
class Factor_N_Days_Ago(CustomFactor):
def compute(self, today, assets, out, input_factor):
out[:] = input_factor[0]
To get the desired '6 month returns' but from 3 months ago one could do something like this.
# Create our base factor. In this case 6 month returns (126 trading days is about 6 months)
returns_6_month = Returns(window_length=126, mask=base_universe)
# This is what our returns factor was 3 months ago (63 trading days is about 3 months)
days_ago = 63
returns_6_month_3_months_ago = Factor_N_Days_Ago([returns_6_month], window_length=days_ago+1, mask=base_universe)
See the attached notebook. Good luck.