Welcome to part 14 of the Python for Finance tutorial series, using Quantopian. In this tutorial, we're going to be covering how to actually place an order for stock (buy/sell/short).
Up to this point, we have the following code:
def initialize(context): context.aapl = sid(24) def handle_data(context,data): # prices for aapl for the last 50 days, in 1 day intervals hist = data.history(context.aapl,'price', 50, '1d') # mean of the entire 50 day history sma_50 = hist.mean() # mean of just the last 50 days sma_20 = hist[-20:].mean()
What we've done so far is define what context.aapl is, then we grabbed historical prices for AAPL, and, using those prices, generated some code to calculate, at every interval, what the 50 and 20 simple moving averages are. Our plan is to create a simple moving average crossover strategy, and we're almost ready. The logic should be simple: If the 20 SMA is greater than the 50 SMA, then the price is rising, and we want in on that action! If the 20 SMA is less than the 50 SMA, then price is falling, and we want to short (bet against) the company. Let's build an ordering system to reflect this:
if sma_20 > sma_50: order_target_percent(context.aapl, 1.0) elif sma_20 < sma_50: order_target_percent(context.aapl, -1.0)
order_target_percent function is used to allow us to invest a certain percentage of our portfolio value into a company. In this case, the only company we're even consider is Apple (AAPL), so we're using 1.0 (100%). There are many methods for ordering, this is just one of them. We can do market orders, order specific dollar amounts, order percents, order a target value, and of course cancel pending orders. We're expecting to simply buy/sell 100% of our shares at each step in this case. If we run this, we get:
Holy smokes! Jackpot! We're gonna be rich!
...just not with this strategy.
When you first write an algorithm, especially when starting out, something like this is fairly likely to occur. Maybe it's in your favor, or maybe you lost 1000% of your starting money, and you're wondering what happened. In this case, it is super easy to spot. First off, our returns are incredibly unlikely, and, given just the basic readout from Quantopian, we can see that immediately we're doing transactions to the tune of multiple 10's of millions of dollars, and even hundreds of millions of dollars, when our starting capital was meant to be $1 million.
So what happened here? Quantopian is built to allow you to do whatever you want, and there are no restrictions on "loans." When you take on loans to invest with in the world of finance, it's usually referred to as leverage. This account is severely over leveraged, and it's exactly what we asked for.
Learning how to diagnose this, and hopefully avoid this in the future is extremely important!
The first step is to pretty much always record leverage. Let's do that now:
def initialize(context): context.aapl = sid(24) def handle_data(context,data): hist = data.history(context.aapl,'price', 50, '1d') sma_50 = hist.mean() sma_20 = hist[-20:].mean() if sma_20 > sma_50: order_target_percent(context.aapl, 1.0) elif sma_20 < sma_50: order_target_percent(context.aapl, -1.0) record(leverage = context.account.leverage)
With record, we can track up to five values. In this case, we're just choosing one. We're looking at our account's leverage, which is automatically tracked for us in
context.account.leverage. You can see other options there, just by doing
context.account. and so on to see with the auto-complete what your choices are. You can use record to track other values as well, this is just an example.
Just running this for a moment, we can see leverage is indeed getting way out of hand:
Okay, so we're over leveraged. What actually happened? Well, for one, this
handle_data function is being run every single minute. Thus, every minute, we can be plausibly ordering, and, in this case, that's ordering 100% of our portfolio's value. We think we're safe, because we're ordering a target percentage. If the target percentage is 100%, how are we winding up with so much more? The problem is, it can take time for orders to actually fill. Thus, as one order is waiting to be filled, others are being made at the same time!
The first thing we might want to do to avoid this, is to use the
get_open_orders() method, as follows:
open_orders = get_open_orders() if sma_20 > sma_50: if context.aapl not in open_orders: order_target_percent(context.aapl, 1.0) elif sma_20 < sma_50: if context.aapl not in open_orders: order_target_percent(context.aapl, -1.0)
Now, before every order, we're checking to see if we have any pending orders already for this company. Let's run that.
One note that I'd like to make is, there's really no way for you to know that
get_open_orders() exists, unless you read the docs. I will show you quite a few methods and functions, but I certainly wont cover them all. Definitely make sure you scroll through the Quantopian API Docs to see what's available to you. You don't need to read it all, just glaze through it and read about the functions that catch your eyes. The functions/methods are in red, so it's really easy to catch them as you scroll through.
The results of that run:
The deviations you see are like 1 +/- 0.0001. We effectively maintained a leverage of 1 this entire time, as we wanted, but...hmm...that return is not so hot!
One thing we can see by clicking on "Transaction Details" on the left nav is that we're making many trades a day. We can see some of our transaction bars are also quite large, sometimes almost $10 million. What's happening here? We also thought we were supposed to be just trading once a day at most.
handle_data function is running every minute, so, again, we actually can still be placing trades every minute. If we're looking to do things that aren't assessing the market every minute, we might actually be looking instead to
schedule functions. Luckily we can do that, and that's the topic of the next tutorial!