Back to Community
Log Normal Return Built-in Factor

Maybe this has already been solved and I am fairly new to this, however, it would be nice if the built-in factor Returns() had a log-normal version (log_return = ln(close/close[1]). I'm struggling with recieving 'window safe" errors when defining my pipeline and haven't found a way around it yet. It just does not like my custom factor for the log-return. Perhaps it is just a datatype issue but I saw some documentation that said only the Return(), Zscore(), and some other built-in factors were the only window safe factors available. I included my custom factor below to illustrate. I want to use that LogReturn() custom factor as an input to other custom factors but the custom factors choke on this whereas they work just fine with Returns(); but that is pct_change and that is not what I want.

I'm not even sure why we use the pct_change version of the returns since to my limited understanding they don't really work for quant purposes since they don't multiply (cumproduct) across different time series accurately like the log-returns do; they are different quantities. I want to do some future price expected value and confidence interval forecasting based on the drift rate and standard deviation of the log-returns so any inaccuracies in the returns have a significant compounding effect.

I appreciate any help you can give. Thanks!

class LogReturn(CustomFactor):
# Default inputs
inputs = [USEquityPricing.close]
window_length = 2
# Compute log return
def compute(self, today, assets, out, close):
logreturns = pd.Series(np.log(close[-1] / close[0]), name = 'logreturns')
out[:] = logreturns #np.log(close[-1] / close[0])

10 responses

I personally use this code:

class LogReturns(CustomFactor):  
    #inputs = [Returns(window_length=2)]  
    window_length = 1  
    window_safe = True  
    def compute(self, today, assets, out, returns):  
        out[:] = np.log( (returns+1.) )

# daily returns  
ret1 = Returns(window_length=2, mask=mask)  
ret1 = LogReturns(inputs=[returns], mask=mask)

# 50 days returns  
ret2 = Returns(window_length=51, mask=mask)  
ret2 = LogReturns(inputs=[returns], mask=mask)  

Luca, this appears to take log transform of the built-in factor Returns(), which is a percent difference. I don't think the nat log of the percent difference (Return() =
np.log((close-close[-1])/close[-1])) is the same as the nat log of the ratio of today's price to yesterday's (np.log(close/close[-1])); which will result in compoundin errors.

How does your script address this apparent discrepancy? Do you have any other ideas around this?

This video illustrates the difference between the two return cal ulation methods: https://youtu.be/PtoUlt3V0CI

Ah, I see where it works. Since Return() brings back the % change (Return() = (close[1]-close)/close[1]), this means that close[1] = close/(1+Return()). So the log(close/close[1]) is also equal to log(close*(1+Return())/close) by substitution; which simply equals log(1+Return()); which is what you have in your script.

Thanks for the help. My thinking was stuck in a box.

Luca's custom factor is correct. Note that he adds 1 to the Arithmetic Return

out[:] = np.log( (returns+1.) )

Here's the proof...

The definitions of Logarithmic and Arithmetic Return (see https://en.wikipedia.org/wiki/Rate_of_return) are

Logarithmic_Return = ln(Vf/Vi)  
Arithmetic Return = (Vf-Vi)/Vi  

where Vi and Vf are the initial and final asset values respectively.

The Arithmetic Return can be re-written as

Arithmetic_Return = Vf/Vi - Vi/Vi =  Vf/Vi - 1

Move the 1 to solve for Vf/Vi and we get

Vf/Vi = Arithmetic_Return + 1

Now, substitute this for Vf/Vi in the Logarithmic Return formula and voila…

Logarithmic_Return = ln(Arithmetic_Return + 1)

log(a/b) = log(a) - log(b)

@Jonathon @Luca there's actually an even more efficient way to calculate log returns: the Factor base class has a log1p method. I've attached a notebook with a quick example and a list of other similar scalar functions.

Loading notebook preview...
Notebook previews are currently unavailable.
Disclaimer

The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by Quantopian. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. No information contained herein should be regarded as a suggestion to engage in or refrain from any investment-related course of action as none of Quantopian nor any of its affiliates is undertaking to provide investment advice, act as an adviser to any plan or entity subject to the Employee Retirement Income Security Act of 1974, as amended, individual retirement account or individual retirement annuity, or give advice in a fiduciary capacity with respect to the materials presented herein. If you are an individual retirement or other investor, contact your financial advisor or other fiduciary unrelated to Quantopian about whether any given investment idea, strategy, product or service described herein may be appropriate for your circumstances. All investments involve risk, including loss of principal. Quantopian makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances.

Thanks ! Yes, I realized what he did shortly after my comment. A more efficient method is even more helpful.

Actually, what is weird is that all I had to do to what I had was add the "window_safe = True" line to all my custom factors and I could pass my original LogReturn custom factor into them. I haven't gotten far enough in the documentation to really understand the window_safe feature. So it looks like all these methods yield equivalent results. Although I had some adjusting to do to get yours to run, I finally figured it out.

Thanks for all your help on this. I learned a lot !

Here is what [David Michalowicz said][1] about using factors as input of other factor:

"[...] is now allowed for a selected few factors deemed safe for use as inputs. This includes Returns and any factors created from rank or zscore. The main reason that these factors can be used as inputs is that they are comparable across splits. Returns, rank and zscore produce normalized values, meaning that they can be meaningfully compared in any context."

So, window_safe = True is a flag that tells Pipeline what factors are safe to be used as input to other factors