Factors

A factor is a function from an asset and a moment in time to a number.

F(asset, timestamp) -> float

In Pipeline, Factors are the most commonly-used term, representing the result of any computation producing a numerical result. Factors require a column of data and a window length as input.

The simplest factors in Pipeline are built-in Factors. Built-in Factors are pre-built to perform common computations. As a first example, let's make a factor to compute the average close price over the last 10 days. We can use the SimpleMovingAverage built-in factor which computes the average value of the input data (close price) over the specified window length (10 days). To do this, we need to import our built-in SimpleMovingAverage factor and the USEquityPricing dataset.


In [1]:
from quantopian.pipeline import Pipeline
from quantopian.research import run_pipeline

# New from the last lesson, import the USEquityPricing dataset.
from quantopian.pipeline.data.builtin import USEquityPricing

# New from the last lesson, import the built-in SimpleMovingAverage factor.
from quantopian.pipeline.factors import SimpleMovingAverage

Creating a Factor

Let's go back to our make_pipeline function from the previous lesson and instantiate a SimpleMovingAverage factor. To create a SimpleMovingAverage factor, we can call the SimpleMovingAverage constructor with two arguments: inputs, which must be a list of BoundColumn objects, and window_length, which must be an integer indicating how many days worth of data our moving average calculation should receive. (We'll discuss BoundColumn in more depth later; for now we just need to know that a BoundColumn is an object indicating what kind of data should be passed to our Factor.).

The following line creates a Factor for computing the 10-day mean close price of securities.


In [2]:
mean_close_10 = SimpleMovingAverage(inputs=[USEquityPricing.close], window_length=10)

It's important to note that creating the factor does not actually perform a computation. Creating a factor is like defining the function. To perform a computation, we need to add the factor to our pipeline and run it.

Adding a Factor to a Pipeline

Let's update our original empty pipeline to make it compute our new moving average factor. To start, let's move our factor instantatiation into make_pipeline. Next, we can tell our pipeline to compute our factor by passing it a columns argument, which should be a dictionary mapping column names to factors, filters, or classifiers. Our updated make_pipeline function should look something like this:


In [3]:
def make_pipeline():
    
    mean_close_10 = SimpleMovingAverage(inputs=[USEquityPricing.close], window_length=10)
    
    return Pipeline(
        columns={
            '10_day_mean_close': mean_close_10
        }
    )

To see what this looks like, let's make our pipeline, run it, and display the result.


In [4]:
result = run_pipeline(make_pipeline(), '2015-05-05', '2015-05-05')
result


Out[4]:
10_day_mean_close
2015-05-05 00:00:00+00:00 Equity(2 [AA]) 13.559500
Equity(21 [AAME]) 3.962500
Equity(24 [AAPL]) 129.025700
Equity(25 [AA_PR]) 88.362500
Equity(31 [ABAX]) 61.920900
Equity(39 [DDC]) 19.287072
Equity(41 [ARCB]) 37.880000
Equity(52 [ABM]) 32.083400
Equity(53 [ABMD]) 66.795000
Equity(62 [ABT]) 47.466000
Equity(64 [ABX]) 12.919000
Equity(66 [AB]) 31.547000
Equity(67 [ADSK]) 60.212000
Equity(69 [ACAT]) 36.331000
Equity(70 [VBF]) 18.767000
Equity(76 [TAP]) 74.632000
Equity(84 [ACET]) 19.873000
Equity(86 [ACG]) 7.810000
Equity(88 [ACI]) 0.996100
Equity(100 [IEP]) 91.821200
Equity(106 [ACU]) 18.641000
Equity(110 [ACXM]) 18.045500
Equity(112 [ACY]) 11.571000
Equity(114 [ADBE]) 76.072000
Equity(117 [AEY]) 2.423400
Equity(122 [ADI]) 63.205900
Equity(128 [ADM]) 48.788500
Equity(134 [SXCL]) NaN
Equity(149 [ADX]) 14.150500
Equity(153 [AE]) 54.099000
... ...
Equity(48961 [NYMT_O]) NaN
Equity(48962 [CSAL]) 29.992000
Equity(48963 [PAK]) 15.531875
Equity(48969 [NSA]) 13.045000
Equity(48971 [BSM]) 17.995000
Equity(48972 [EVA]) 21.413250
Equity(48981 [APIC]) 14.814000
Equity(48989 [UK]) 24.946667
Equity(48990 [ACWF]) 25.250000
Equity(48991 [ISCF]) 24.985000
Equity(48992 [INTF]) 25.030000
Equity(48993 [JETS]) 24.579333
Equity(48994 [ACTX]) 15.097333
Equity(48995 [LRGF]) 24.890000
Equity(48996 [SMLF]) 29.456667
Equity(48997 [VKTX]) 9.115000
Equity(48998 [OPGN]) NaN
Equity(48999 [AAPC]) 10.144000
Equity(49000 [BPMC]) 20.810000
Equity(49001 [CLCD]) NaN
Equity(49004 [TNP_PRD]) 24.750000
Equity(49005 [ARWA_U]) NaN
Equity(49006 [BVXV]) NaN
Equity(49007 [BVXV_W]) NaN
Equity(49008 [OPGN_W]) NaN
Equity(49009 [PRKU]) NaN
Equity(49010 [TBRA]) NaN
Equity(49131 [OESX]) NaN
Equity(49259 [ITUS]) NaN
Equity(49523 [TLGT]) NaN

8236 rows × 1 columns

Now we have a column in our pipeline output with the 10-day average close price for all 8000+ securities (display truncated). Note that each row corresponds to the result of our computation for a given security on a given date stored. The DataFrame has a MultiIndex where the first level is a datetime representing the date of the computation and the second level is an Equity object corresponding to the security. For example, the first row (2015-05-05 00:00:00+00:00, Equity(2 [AA])) will contain the result of our mean_close_10 factor for AA on May 5th, 2015.

If we run our pipeline over more than one day, the output looks like this.


In [5]:
result = run_pipeline(make_pipeline(), '2015-05-05', '2015-05-07')
result


Out[5]:
10_day_mean_close
2015-05-05 00:00:00+00:00 Equity(2 [AA]) 13.559500
Equity(21 [AAME]) 3.962500
Equity(24 [AAPL]) 129.025700
Equity(25 [AA_PR]) 88.362500
Equity(31 [ABAX]) 61.920900
Equity(39 [DDC]) 19.287072
Equity(41 [ARCB]) 37.880000
Equity(52 [ABM]) 32.083400
Equity(53 [ABMD]) 66.795000
Equity(62 [ABT]) 47.466000
Equity(64 [ABX]) 12.919000
Equity(66 [AB]) 31.547000
Equity(67 [ADSK]) 60.212000
Equity(69 [ACAT]) 36.331000
Equity(70 [VBF]) 18.767000
Equity(76 [TAP]) 74.632000
Equity(84 [ACET]) 19.873000
Equity(86 [ACG]) 7.810000
Equity(88 [ACI]) 0.996100
Equity(100 [IEP]) 91.821200
Equity(106 [ACU]) 18.641000
Equity(110 [ACXM]) 18.045500
Equity(112 [ACY]) 11.571000
Equity(114 [ADBE]) 76.072000
Equity(117 [AEY]) 2.423400
Equity(122 [ADI]) 63.205900
Equity(128 [ADM]) 48.788500
Equity(134 [SXCL]) NaN
Equity(149 [ADX]) 14.150500
Equity(153 [AE]) 54.099000
... ... ...
2015-05-07 00:00:00+00:00 Equity(48981 [APIC]) 14.646000
Equity(48989 [UK]) 24.878000
Equity(48990 [ACWF]) 25.036667
Equity(48991 [ISCF]) 24.875000
Equity(48992 [INTF]) 24.813000
Equity(48993 [JETS]) 24.343600
Equity(48994 [ACTX]) 15.020400
Equity(48995 [LRGF]) 24.788000
Equity(48996 [SMLF]) 29.370000
Equity(48997 [VKTX]) 9.232500
Equity(48998 [OPGN]) 4.950000
Equity(48999 [AAPC]) 10.167000
Equity(49000 [BPMC]) 20.906667
Equity(49001 [CLCD]) 8.010000
Equity(49004 [TNP_PRD]) 24.633333
Equity(49005 [ARWA_U]) 10.010000
Equity(49006 [BVXV]) NaN
Equity(49007 [BVXV_W]) NaN
Equity(49008 [OPGN_W]) 0.817500
Equity(49009 [PRKU]) NaN
Equity(49010 [TBRA]) NaN
Equity(49015 [ADAP]) NaN
Equity(49016 [COLL]) NaN
Equity(49017 [GLSS]) NaN
Equity(49018 [HTGM]) NaN
Equity(49019 [LRET]) NaN
Equity(49020 [MVIR]) NaN
Equity(49131 [OESX]) NaN
Equity(49259 [ITUS]) NaN
Equity(49523 [TLGT]) NaN

24705 rows × 1 columns

Note: factors can also be added to an existing Pipeline instance using the Pipeline.add method. Using add looks something like this:

>>> my_pipe = Pipeline()
>>> f1 = SomeFactor(...)
>>> my_pipe.add(f1)

Latest

The most commonly used built-in Factor is Latest. The Latest factor gets the most recent value of a given data column. This factor is common enough that it is instantiated differently from other factors. The best way to get the latest value of a data column is by getting its .latest attribute. As an example, let's update make_pipeline to create a latest close price factor and add it to our pipeline:


In [6]:
def make_pipeline():

    mean_close_10 = SimpleMovingAverage(inputs=[USEquityPricing.close], window_length=10)
    latest_close = USEquityPricing.close.latest

    return Pipeline(
        columns={
            '10_day_mean_close': mean_close_10,
            'latest_close_price': latest_close
        }
    )

And now, when we make and run our pipeline again, there are two columns in our output dataframe. One column has the 10-day mean close price of each security, and the other has the latest close price.


In [7]:
result = run_pipeline(make_pipeline(), '2015-05-05', '2015-05-05')
result.head(5)


Out[7]:
10_day_mean_close latest_close_price
2015-05-05 00:00:00+00:00 Equity(2 [AA]) 13.5595 14.015
Equity(21 [AAME]) 3.9625 NaN
Equity(24 [AAPL]) 129.0257 128.699
Equity(25 [AA_PR]) 88.3625 NaN
Equity(31 [ABAX]) 61.9209 55.030

.latest can sometimes return things other than Factors. We'll see examples of other possible return types in later lessons.

Default Inputs

Some factors have default inputs that should never be changed. For example the VWAP built-in factor is always calculated from USEquityPricing.close and USEquityPricing.volume. When a factor is always calculated from the same BoundColumns, we can call the constructor without specifying inputs.


In [8]:
from quantopian.pipeline.factors import VWAP
vwap = VWAP(window_length=10)

In the next lesson, we will look at combining factors.