Back to Community
SPY 50/200 SMA crossover

Good afternoon everybody. I am new to Quantopian and got started on this yesterday. I think that what the algo does now is that it gets long (all the money in the account) when the 50 sma crosses above the 200sma (selling those shares when the 50 sma crosses below the 200 sma) and when the 50 sma crosses below the 200 sma, it shorts 10000 shares of SPY and covers when the 50 sma crosses back above the 200 sma.

One of the problems I am having is that I would like for the algo to short the total account value (similar to when it gets long the entire account value--I have that named bull_shares) instead of the 10,000 shares I have it shorting now. I tried a similar approach naming it bear_shares and having it equal

-round(context.portfolio.cash/price,0) 

but that proved fruitless.

Second, I seem to be having log file problems as well. I notice that in the beginning the log files work great, it gets long X shares and sells X shares on Y date then Shorts Z shares on Y date. But then after a few entries I notice it having 2 buy entries in a row (which shouldn't be possible without a sell and short order in between). Any comments or help would be greatly appreciated guys, thank you.

Also, I am very new to python as well, so if my code is off in any obvious manner, I apologize ahead of time :-)

John

must denote two seperate states for long and short, called context.bull and  
context.bear.  Or else the algo's long and short side will overlap. Couldn't think  
of a better way to do this.

def initialize(context):  
    context.sid = sid(8554)  
    context.bull = False  
    context.bear = False  
    context.spy = sid(8554)  
    context.sid_names = {8554: "SPY"}

def handle_data(context, data):  
    short = data[context.sid].mavg(8)  
    mid = data[context.sid].mavg(21)  
    long = data[context.sid].mavg(50)  
    mlong = data[context.sid].mavg(100)  
    slong = data[context.sid].mavg(200)  
    price = data[context.sid].price  
    bull_shares = round(context.portfolio.cash/price,0)  
    sid_name = context.sid_names  
      #this is the 'long' version of the algo  
    if (long > slong) and not context.bull:  
        order(context.sid, bull_shares)  
        context.bull_shares = bull_shares  
        context.bull = True  
        log.info("BOT " + str(bull_shares) + " shares of " + str(sid_name) + " at " + str(price) + ".")  
    if (long < slong) and context.bull:  
        order(context.sid, -context.bull_shares)  
        context.bull = False  
        log.info("SOLD " + str(-context.bull_shares) + " shares of " + str(sid_name) + " at " + str(price) + ".")  
       #this is where the 'long' algo ends  
    record(short_mavg = short,  
        long_mavg = long,  
        mid_mavg = mid,  
        slong_mavg = slong,  
        spy_price = price)  
        #this is the 'short' version of the algo  
    if (long < slong) and not context.bear:  
        order(context.sid, -10000)  
        context.bear = True  
        log.info("SHT 10000 shares of " + str(sid_name) + " at " + str(price) + ".")       

    if (long > slong) and context.bear:  
        order(context.sid, 10000)  
        context.bear = False  
        log.info("COVRD 10000 shares of "+ str(sid_name) + " at " + str(price) + ".")  
         #this is where the 'short' algo ends  
7 responses

I have noticed two buy orders in a row as well.

Sometimes the entire amount you enter for the order is not able to be purchased within the same minute you create the buy. The balance will be purchased on subsequent minutes.

If you have an idea of the value of your portfolio, you can do the math to see if the two consecutive buys would cover your initial buy request.

One problem I see with your function determining how many shares to buy is that it rounds. That means that if you can buy 55.7 shares, your program will buy 56 shares even though it doesn't have the capital. Also, I'm not sure if this applies here, but you want to make sure you aren't dividing two integers, because that doesn't behave normally. Try it out: round(55/7) returns 7.0 While round(float(55)/7) returns 8.0 since we are dividing a float and an int. Again, not sure if this applies here, but I think it's good to know if you are using round.

As for orders happening twice, I think Chuck is right. One way to get started dealing with this problem is to count the amount of shares in-queue. You can do this with:

tot = 0  
for o in get_open_orders(sid=sid):  
    tot += o.amount  

And the tot variable should contain the amount of open orders you have. Does that help?

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.

Hi John,

Interesting algo, thanks for sharing. I copied your code into an algo, make a couple modifications, and then shared it below. A few thoughts:

  • There's nothing in your algo that explicitly says that a "cover" has to come before a "buy." When the relationship between short and long swaps, then both the long and the short aspects of your algorithm are activated. The code runs through the sections in order, and "buy" is before "cover" so it executes in that order. You'd have to change your variables to unify the long and short aspects, or you might use an if/elif construction so it can only do one type or the other.
  • I suspect your attempts to compute -round(context.portfolio.cash/price,0) were failing because at the time you computed them, I suspect your cash was very close to zero.

There are a bunch of different ways to tackle it, but to get the behavior you want, you have to explicitly change the code so that you can only go long or short if the previous position is already closed, and also only calculate the size of your "next" order when the old one is closed.

Clone Algorithm
50
Loading...
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
def initialize(context):  
    context.sid = sid(8554)  
    context.bull = False  
    context.bear = False  
    context.spy = sid(8554)  
    context.sid_names = {8554: "SPY"}

def handle_data(context, data):  
    short = data[context.sid].mavg(8)  
#    mid = data[context.sid].mavg(21)  
    long = data[context.sid].mavg(50)  
#    mlong = data[context.sid].mavg(100)  
    slong = data[context.sid].mavg(200)  
    price = data[context.sid].price  
    bull_shares = round(context.portfolio.cash/price,0)  
    sid_name = context.sid_names  
      #this is the 'long' version of the algo  
    if (long > slong) and not context.bull:  
        order(context.sid, bull_shares)  
        context.bull_shares = bull_shares  
        context.bull = True  
        log.info("BOT " + str(bull_shares) + " shares of " + str(sid_name) + " at " + str(price) + ".")  
    if (long < slong) and context.bull:  
        order(context.sid, -context.bull_shares)  
        context.bull = False  
        log.info("SOLD " + str(-context.bull_shares) + " shares of " + str(sid_name) + " at " + str(price) + ".")  
       #this is where the 'long' algo ends  

        #this is the 'short' version of the algo  
    if (long < slong) and not context.bear:  
        order(context.sid, -10000)  
        context.bear = True  
        log.info("SHT 10000 shares of " + str(sid_name) + " at " + str(price) + ".")       
    if (long > slong) and context.bear:  
        order(context.sid, 10000)  
        context.bear = False  
        log.info("COVRD 10000 shares of "+ str(sid_name) + " at " + str(price) + ".")  
         #this is where the 'short' algo ends  
    record(short_mavg = short,  
        long_mavg = long,  
 #       mid_mavg = mid,  
        slong_mavg = slong,
        bull_signal = int(context.bull)*100,
        bear_signal = int(context.bear)*100)  
  #      spy_price = price)              
This backtest was created using an older version of the backtester. Please re-run this backtest to see results using the latest backtester. Learn more about the recent changes.
There was a runtime error.
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 for all your responses here. Greatly appreciated. The real trouble I am having here is that I can't figure out another way to get short the entire account value, like I figured out with the long side--since the flip happens at one point in time. The back to back buy or sell orders doesn't bother me too much since they are both taking place at exactly the same time--thus meaning that my risk is zero and my use of borrowing would last a fraction of a second.

The cool thing I like about this algo is its simplicity. People like to get caught up thinking that their algo should be complex and use outlandish mathematical models-- in my mind simple is better. If you backtest this algo i provided starting on jan 1, 2002 (or whatever the first day of data available is) you will be quite surprised.

Anyways, I appreciate your guys' feedback, I'm prob just gonna be learning more python so I can fully understand more of how this site works and really use some of the unique features Quantopian has to offer. Have a good evening!

John

Before I forget...I feel like the rounding problem could be fixed by changing bull_shares to :


 bull_shares = (round(context.portfolio.cash/price,0)-1)

John, if I understand what you're looking for, Dennis has written some code that gets close to what you're looking for:

# calculate capital_used using absolute amount: abs(amount)*cost_basis  
pos = context.portfolio.positions  
abs_capital_used = sum(abs(pos[s].amount) * pos[s].cost_basis for s in pos)  
record(abs_capital_used = abs_capital_used)  
# calculate free cash: starting_cash + pnl - capital_used  
port = context.portfolio  
abs_cash = port.starting_cash + port.pnl - abs_capital_used  
record(abs_cash = abs_cash)  

@John and @Dan, the code fragment for abs_capital_used and abs_cash is useful if you want to know how much capital is used and/or free. Then you know how big of a position you can take to make use of all your remaining capital.

But it's not suitable if you are planning to reverse, rebalance or otherwise replace your existing positions. That is actually much easier.

# total account value at present (including active positions)  
total_capital = context.portfolio.starting_cash + context.portfolio.pnl  
# portion of account value to use for each *new* position (assuming old positions are being closed)  
position_size = total_capital / num_positions  
# number of shares to buy (short) for *this* position  
num_shares = position_size / data[stock].price  
# lets make this a short position (negative quantity)  
num_shares *= -1  
# adjust existing position to match num_shares (even works for shorts and reversals)  
order(stock, num_shares - context.portfolio.positions[stock].amount)