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)