Quantopian's community platform is shutting down. Please read this post for more information and download your code.
Back to Community
Template: Limiting Leverage (by Brandon Ogle and Dan Sandberg)

Hi All -

Mr. Brandon Ogle was kind enough to provide us with a template for limiting the leverage in a long only strategy, which I have amended below to work with a combination of long and short positions. Feel free to put it through its paces and implement it in your algorithms. Note that the generate_marginCall function is currently blank. Obviously if you are holding a number of long and short positions, margin calls can be handled in multiple ways.

Enjoy!



import datetime  
import math  
import numpy  
import pandas

def initialize(context):  
    #  
    #  
    #Variables for later  
    context.day=None  
    context.timestep=0  
    context.margin_req=0  
    context.totalShorts=0  
    context.totalLongs=0  
    context.cash=0  
    #  
    #  
    #Build the Universe  
    #Add SPDR SPY  
    context.SPY = sid(8554)  
    context.SHY = sid(23911)

    #  
    #  
    #Set constraints on borrowing  
    context.pct_invested_threshold = 0.95 #Set limit on percent invested (as a decimal)  
    context.init_margin=1.50 #Set initial margin requirement  
    context.maint_margin=1.25 #Set the maintenance margin requirement   

def handle_data(context, data):

    #Update Account Information at the beginning of each frame  
    update_newFrame(context, data)  


    #Apply Trade Logic  
    trade_logic(context,data)


#    Trade Algorithm  
def trade_logic (context, data):  
    nshares=(context.pct_invested_threshold * context.cash / context.init_margin / data[context.SPY].price)  
    generate_order (context.SPY, -nshares, context, data)  
#    generate_order (context.SHY, -2500, context, data)


#  
#  
#    Supporting Functions  
#  
#

def pause(nth_Day, mycontext, mydata):  
    if mycontext.timestep == 1:  
        nshares=math.floor (mycontext.portfolio.cash / mydata[mycontext.SPY].price)  
        order(mycontext.SPY,+nshares)  
#        log.info("First timestep and paused. Ordering "+str(nshares)+" of SPY.")  
    if mycontext.timestep < nth_Day:  
        return(0)  
    elif mycontext.timestep == nth_Day:  
        liquidate_position(mycontext.SPY,mycontext)  
        return(1)  
    else:  
        return(1)

def update_newFrame(context, data):  
    #  
    context.cash = context.portfolio.cash  
    context.portvalue = context.portfolio.positions_value  
    context.totalShorts=0  
    for sym in data.keys():  
        if context.portfolio.positions[sym].amount < 0:  
            context.totalShorts += (context.portfolio.positions[sym].amount * data[sym].price)  
        else:  
            context.totalLongs += (context.portfolio.positions[sym].amount * data[sym].price)

    update_portvals(context)  
    #Handle assigning the timestep number (1 day is 1 timestep)  
    if get_datetime().day <> context.day: #look for a new day  
        context.day=get_datetime().day  
        context.timestep += 1  
        #log.info ( "Cash: "+str(context.cash)+"; Margin Req: "+str(context.margin_req)+" Avail Cash:"+str(context.cash - context.margin_req) )  
        if context.cash < context.margin_req: #check for margin calls daily  
            generate_marginCall(context, data)  

def update_portvals(context):  
    #Update account information when this function is called  
    context.total_equity = context.cash + context.portvalue  
    context.pct_invested = (context.totalLongs-context.totalShorts) / context.total_equity  
    context.pct_cash = context.cash / context.total_equity  
    context.margin_req = abs(context.totalShorts * context.maint_margin)  

def generate_order(sym, size, context, data):  
    if size>0: #Opening a long position    

        #log.info("Buy long "+str(size)+" shares "+str(sym) )  
        #log.info("Cash = "+str(context.cash)+"; Current Margin Req.="+str(context.margin_req) )                 

        #Is there enough available cash to buy the position  
        if (context.cash-context.margin_req) < size * data[sym].price:  
            #log.info("Trade canceled : Insufficient funds.")  
            return

        #Deduct price from cash and add to portfolio value  
        context.cash -= size * data[sym].price  
        context.portvalue += size * data[sym].price  
        context.totalLongs += size * data[sym].price  
        update_portvals(context)

        #Abort the transaction if the percent invested is greater than the threshold  
        #before slippage and commissions  
        if context.pct_invested > context.pct_invested_threshold:  
            context.cash += size * data[sym].price  
            context.portvalue -= size * data[sym].price  
            context.totalLongs -= size * data[sym].price  
            update_portvals(context)  
            return  
        #Abort the transaction if the investment would generate a margin call  
        if context.cash < context.margin_req:  
            context.cash += size * data[sym].price  
            context.portvalue -= size * data[sym].price  
            context.totalLongs -= size * data[sym].price  
            update_portvals(context)  
            return  
        order(sym,size)

    else: #Opening a short position  
        #log.info("Generating a short order for "+str(size)+" shares of "+str(sym)+" and context.cash="+str(context.cash)+" and context.margin_req="+str(context.margin_req) )  
        #Is there at least enough available cash to cover the initial maintenance margin  
        if (context.cash-context.margin_req) < abs(size * data[sym].price * context.init_margin):  
            #log.info("Trade canceled")  
            return  
        #Deduct price from cash and add to portfolio value (note that size is negative)  
        context.cash -= size * data[sym].price  
        context.portvalue += size * data[sym].price  
        context.totalShorts += size * data[sym].price  
        update_portvals(context)  
        #Abort the transaction if the percent invested is greater than the threshold  
        #before slippage and commission  
        if context.pct_invested > context.pct_invested_threshold:  
            context.cash += size * data[sym].price  
            context.portvalue -= size * data[sym].price  
            context.totalShorts -= size * data[sym].price  
            update_portvals(context)  
            #log.info("Trade canceled")  
            return  
        #Abort the transaction if the investment would generate a margin call  
        if context.cash < context.margin_req:  
            context.cash += size * data[sym].price  
            context.portvalue -= size * data[sym].price  
            context.totalShorts -= size * data[sym].price  
            update_portvals(context)  
            #log.info("Trade canceled")  
            return  
        order(sym,size)  

def generate_marginCall(context,data):  
    #This function should be coded to address margin calls  
    log.info("Margin call")  


def liquidate_position(sym,context):  
    if context.portfolio.positions[sym].amount is not 0:  
        order(sym, -context.portfolio.positions[sym].amount)  
5 responses

This looks pretty awesome. Thanks for sharing it.

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.

Again, good work. Stop and limit orders could also easily be added to this. If I get some other ideas or need additional features from this I'll try to add them and post the modified version. It would be cool if this was a framework that people worked on that was constantly getting improved.

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.

Daniel,

Thanks for the shout out. Its awesome you extended this to L/S, I hope that this so called 'template' starts making its way into other users code. I like that you cleaned up handle_data() by moving all the maintenance code to update_newFrame(). The added realism in backtesting would certainly improve the content of the community. I also hope accurate backtests will prevent users from blowing up their accounts once Live Trading gets out of beta.

@Gus, I would love to see this evolve through time with the community. The prototyping time would cut down immensely if we had a standardized framework that handled all the minutiae.

Thanks Dan, Gus, and Brandon. Agreed on all fronts, especially regarding an evolving template to which users can add functions.

@Gus - stop and limit orders are definitely easily added. Generate_Limit_order would include an extra passed variable for the limit price and that would be the price you would use to calculate the effect on the portfolio leverage (rather than the current price of the security). For stop orders you would need to keep an array of stop orders with stop prices and when the stop price is met you would call generate_order (since it would become a market order at this point)

Another feature I'd like to add at some point would be a feature within Generate_Order that effectively scales down the magnitude of the order if it is rejected. For example

def generate_order(sym, size, context, data):  
    if size>0: #Opening a long position    

        #log.info("Buy long "+str(size)+" shares "+str(sym) )  
        #log.info("Cash = "+str(context.cash)+"; Current Margin Req.="+str(context.margin_req) )                 

        #Is there enough available cash to buy the position  
        if (context.cash-context.margin_req) < size * data[sym].price:  
            #log.info("Trade canceled : Insufficient funds.")  
            **generate_order(sym, 0.9*size, context, data)**  
            return  

Of course you would need to handle be trapped in an indefinitely loop where you essentially can't afford not a single additional share and you repeatedly call the function. I suppose at some point size < 0.5 and therefore rounds to 0?

Hey guys great work so far!

I agree that it would be nice to see an easy way to expand the template. I've created a simple github repo where I plan to build out a general template. So far it is simply this template but I have separated out concerns.

https://github.com/h55nick/quantopian-template

You simply run create.sh to compile into quantopian.py

Nick