All right. Well, welcome back. This is going to be something of a long session I suspect, and I really did consider breaking this up somewhere along the way but I like the momentum of going right from the beginning and implementing the CPPI algorithm as well as going all the way to adding drawdown constraints, and I liked the momentum of doing it all in one shot. So I'm going to do it that way. Obviously, you can stop whenever you want and hopefully this will turn out to be okay. So let's get started with the standard way that we've been doing this ever since we started. We're going to import the standard stuff. I'm also going to load in the industry returns and I'm going to load up the total market index returns. This is the total market index that we created, the cap weighted index that we created in a previous class, previous session. So what is the basic idea in CPPI? It's a dynamic algorithm. So we're going to dynamically allocate between a risky asset and a safe asset with the goal of making sure that that combined portfolio that consists of some allocation to the risky asset and some allocation to the safe asset does not violate a floor constraint, does not go below a certain number. That's the basic algorithm. So what do we need for that? Obviously, we need a risky asset. So I'm going to start by assigning my risky asset, so I need the returns of the risky asset and I'm going to use the industry returns for that. That's why I load it up. Let's just take a few of them because I don't want this to be too larger a dataset for now. So it's 2,000 onwards is what I was thinking I would do. We're going to take a subset, and I'm not going to have load it up with all 30 industries to begin with, I'll just pick a few. So let's take steel industry because it's changed so much. Of course, I like finance because well, it's ours, and what else do I like? Well, I definitely like beer. So those are the industries that I'm going to use and I'm going to be using as my test data. That's the risky asset. Now, we also need the safe asset, so we have to invent that. So my safe returns are what? I'm going to create a DataFrame, and what am I going to do? I'm going to do a little trick here. So what I'm going to need is, I'm going to run the algorithm for the risky assets one at a time. For each one of those, I need a safe asset. Now in my case, I'm just going to use something like three percent or four percent per year. But I need another set of returns that have the exact same shape as this risky_r. So let me be specific about it. What I'm saying here is let me do that. So if I look at the shape of the risky_r data, the risky returns, I've got three columns and 228 rows. Now, what I could do is I could just create a DataFrame of that shape or NumPy array of that shape and then stuff it. But I just wanted to show you this little trick that you can do. If you want to make another DataFrame that has the exact same shape as a DataFrame that you already have, the easiest way to do it is just say reindex_like and give it the DataFrame that you already have. So that will give you a DataFrame that has exactly the same shape. In fact, that's just fine. Just to see that I did that right. Perfect, that was the idea. Now, I have to stuff that with some safe values. So the simplest way to just give everything the same number is something like that, and we're using monthly data. So I'm going to divide by 12. So approximately three percent a year. Now, I need to run the CPPI algorithm. What we're really going to do is basically run a back test of the CPPI algorithm, and then let's start that off with say let's say $1000, and I'm going to set my floor as something like 80 percent. So I could have said 800, but I've noticed that people traditionally use the floor parameter as a fraction of the start parameter. So you can think of this as a floor of 80 percent of the starting value. Good. So now we've got everything that we need. So think about, what is the algorithm at work? Let's quickly identify the steps in the algorithm. So the first step is you have to compute a risk budget. So that's the cushion, and the cushion is what? That's the asset value minus the floor value. So minus floor value. Then the next step along the way is now that you've got the risk budget, we have to compute an allocation to the safe and risky assets. The way we do that, we use m times the risk budget. So you use that sort of multiplier to do that. Then you've taken a dollar value and you've said, I'm going to put this much in the safe asset and I'm going to put this much in the risky asset. Well, your job is done. So all you have to do is recompute the asset value based on the returns, and that's pretty much it. So and we've got to just do this over and over, over time. So we just go for every unit of time starting from say in our example, we're going to go back from I think we said 2000? Yeah. So starting in 2000 January and then in February and then March and so on and so forth. You just want to click through for every single month. So you're probably thinking that well, let's just start with a for loop, and you might be surprised that we've got this far into the course and we've never used a for loop. We are going to use a for loop in this particular case. But I want to actually do a little detour from CPPI to explain why we stay away from for loops when we're working with NumPy and pandas. So forgive this little parenthetical remark that I'm going to make and I'll close the parenthetical remark in a few minutes. But I want you to understand why we haven't seen loops so far, and it has to do with efficiency. So let me give you an example. Let's take the simple example of compounding a bunch of returns and we've done this several times. So I'm going to just type out the function that implicitly we've been using. So if you give me a sequence of returns r and I want to compound it, and I'll just skip the docstring because it's just an example. What would I do? So we've done this actually several times. We say return and then you take the one plus r format of that, and then you compute the product of that, and then subtract one. So this is something we literally covered I think in the very first session, which is how do you compound a series of bunch of returns? Actually, let's call this compound1 and you'll see in a second why. The reason is, I'm going to do another definition. The same function, I'm going to write it in another way. Let's call this compound two of r, and that is going to be a little bit more obscure, but bear with me. What is this? You are doing a product of the one plus r's and then you're subtracting one. Instead of doing a product, you can also compute the log of the one plus r returns and then you can add them and then reverse that process. So you think wow, that's really obscure. So let's try it. Let's just take it out and see what's going on. So what I'm saying is you can do np.log of one plus r and then you can sum it. That's basically the product now, and then you have to reverse that process. So what you would do is, you would exponentiate, that's the np.exp. That's the inverse of that, and then you do minus one. That make sense? So you can actually make this slightly more efficient because this log of one plus something happens so often, there's actually a built-in function to do that in NumPy and it's one p, log in one plus, and so you can just give it r. Now, why did they do that? So let me go back to this. Why do they do that? The thing is this may not look like a loop to you, but that's a loop. If r is an array, one plus r involves looping through every element of r and producing a new array which is one plus r. So if there's 1,000 elements in r, you have to iterate through 1,000 elements to be able to get the one plus r array. Then you send that 1,000 elements to the log function and then the log gets computed on the 1,000 element array. So the point I'm trying to make is, this may not look like a loop to you, but it's definitely a loop eventually. At the end of the day, someone has to loop. We cannot avoid the loop, but what we can do is push the loop to the most efficient implementation possible. So that's exactly what's going on here, is we're sending that thing to NumPy and say, "Look, I need to do log of one plus this. So can you please do it in the most efficient way you can." So the loop is not going away, it's definitely there. Okay. It's definitely there, but just like there's a log 1p, there's also an expm1, which means exp of this thing minus 1. So there we go. We've got these things. Let's just make sure that these are doing exactly the same thing. So I'm going to do compound1 in return. Actually, let's run it on our risky assets. So I'm going to compound each one of those risky assets, so what should I get? I should get essentially the result of multiplying all of those. So it's the compounded return. So as you can see over this period, you would have lost money if you'd invested in steel. You would have made fair amount of money if you'd invested in finance. You would have done fantastic if you invested in beer companies. Let's do the same thing. I'm going to do compound2 this time of risky r, and I got exactly the same number. So that's exactly what I wanted to see. I want to make the point that these two are basically doing exactly the same thing. Why did I bother with that? Because I want to show you this little piece of magic called timeit, and timeit will basically compute for you how long it takes to run that thing and it's actually smart about how it does it. Because it's actually going to run it not just once, but it's going to run it several times in a loop and then it's going to measure on average how much time it takes to execute that command on average and it's going to execute it several times. So let's do that. I'm going to do exactly that and I'm going to run that. So it takes a little time because as I told you, it's actually going to run it not just once, it's going to run it several times, and it's going to compute the average time that it takes to run that command. It's at this point telling you that it's 1.74 milliseconds per iteration loop, plus or minus 4.92 microseconds. So let's just call it 1.74 milliseconds, and that's based on seven runs of 1000 loops each, etc. So it's giving you the mean and standard deviation. So let's try. This is our standard implementation of compound1 that we've been using. Now, why don't we do the exact same thing? But I'm going to measure compound2. Again, it's going to take a few seconds to run, and it's going to give you a very surprisingly different answer which is, compound2 takes about half the time to run, it's 575 microseconds versus 1.74 milliseconds, it's a third of the time. So that is the reason why we generally avoid doing for loops in Python. If we have a choice, you push the for loop down to the most efficient place to implement the loop and that is definitely not in Python. It is either in NumPy, or something in pandas or in NumPy, or in C. That's the parenthetical remark that I wanted to make. I'd been wanting to make this for the last several classes. I just couldn't find the right moment and so I decided to do it here because we are going to embark on our first honest-to-goodness for loop. We've seen a list of comprehensions, but that's an embedded for loop. But let's now implement our first real for loop. So what do we have to do for the for loop? First, we need to set up some parameters. So now my little detour for, so let me make sure that I marked the fact back to CPPI. So now I'm back to CPPI. So what are we going to do? First, we need to set up these various CPPI parameters. I'm going to iterate over a whole bunch of dates. So what are the dates? So the dates are nothing more than the risky returns index. So those are the dates I'm going to iterate across. So at some point, I'm going to need the number of steps, and what is the number of steps? It's nothing more than the length of that array. How many dates? If I've got two years, then I have got 24 dates, and so the number of steps is 24. I need to compute the account value as I'm going at each step, and I'm going to set that up by starting it out as the start value, and then I have to know what the floor value is. Because remember, what I'm doing here? I'm computing. The cushion is the very first thing I'm going to have to do. So I'm going to compute the asset value minus the floor value. So I need to know what the floor value is. I'm going to initialize that multiplied by the floor because we decided that the floor is a ratio. So it's 0.8. So the floor value when I start here with the parameters that I've chosen so far is going to be 1,000 multiplied by 0.8, and that's $800. What else do I need? To be able to compute the risk budget, I need the multiplier. So let's start that out at three, and I think we're done. But actually I'm going to do something that is really not necessary for the CPPI algorithm, but it is interesting for us to look at as we are running the algorithm, and that is, I'm going to run this algorithm over time. It's a back-test that I'm going to run over time. So what I want to see is at each stage of the back-test, I want to track all these various values that I might want to look at at the end of the back-test. So for example, I might want to look at the history of account values, the history of weights, the history of the risk budget. So I'm just going to start building out an array. So I'm going to say something like account history, and the account history is nothing more than a DataFrame. What is the DataFrame? Well, it is basically exactly the same shape as the risky return itself. I need it for every single one of those columns that I'm going to run the algorithm on and for every single row, I want to know what happened to the account history there. So I'm going to use my same trick that I did before, re-index like, and let's just use risky r, and I'm just going to copy paste a couple of things here. What else do I need? I said I needed the cushion history, and what else do I need? I needed the allocation history. So I need the weight in the risky asset. So let's do risky weight history. I think that's all the setup I need and now we can jump into the actual for loop. So now we get to the core of the CPPI algorithm. So let's jump right into it. So how do you do a for loop? So what do we really want to do? We want to click through for every step of the algorithm, and how many steps do we have? Well, we have n steps. So the way I'm going to do this is I'm going to say for step in, and the way you do this in Python is you call a function called range, and range gives you a sequence of numbers that you can iterate across starting from zero up to the last number. So I'm going to go n steps. So for step in range to n steps. What that is basically saying is I'm going to execute the body of this loop. The body of the loop is going to be executed for first with step. First with step is equal to zero, then with step equal to one, then with step equal to two, all the way up to n steps not including n steps. So it's zero to n steps minus one is how you would code this in perhaps C, or Java, or something like that. So what are we going to do at every step? We're just going to do those three things that we said. First, is compute the risk budget. That's the cushion. What is the cushion? The cushion is nothing more than the account value that we have minus the floor value. So that's the absolute difference between the account value and the floor value, but we want a ratio. So for example, if your floor is at 800 and you're at $1000, that means you have a 20 percent cushion. Did I say that right? Yeah. If your floor is at 800 and your account is at 1000, then the difference between those two is $200, and $200 out of your portfolio value of $1000 is 20 percent. So I want to divide that by the account value. Good. So that is my cushion. Once I've got my cushion, which is my risk budget, what do I do? I have to decide how much money do I give to the risky asset? So I need the weight to allocate to the risky asset. So I'm going to call that the risky weight, and that's nothing more than n times the cushion. So now I know how much money I want to allocate to the risky asset. Well, how much do I allocate to the safe asset? It's nothing more than 1 minus what I have to the risky asset. Now, I'm going to make one little change here. I wouldn't call it a change. Just one little clarification here. So think about this. What happens if your account value is at a 1,000 and your floor is at 600? What is your cushion? Your cushion is 40 percent. It is 1,000 minus 600 which is 400. So it's 400 divided by 1,000 which is 40 percent. So my cushion is 40 percent. Now, if my multiplier is say three, 40 percent multiplied by three is a 120 percent. So the allocation to the risky asset is going to be a 120 percent. Well, that means you have to borrow money. So that's you are levering your investment. We typically don't want to do that. We want to limit the allocation to the risky asset to be no more than a 100 percent. So let me implement that because I like having that constraint, it's much more realistic. Most people are not willing to lever their CPPI investments. So how do you do that? Well, all you have to do is you have to make sure that no matter what you come up with as the risky weight, it's no more than one or no more than a 100 percent. So that means the risky weight is the lower of minimum of whatever I've computed as the risky weight and one. Now, you also want it to be always a positive number. So why don't we just go ahead and do that as well. So you want that to be the maximum of whatever risky weight you've computed and zero. All that saying is these two lines are saying that don't go above a 100 percent and don't go below zero percent. Don't go short. Good. So far we've done everything that we needed to. So now we know how much what fraction of the account value you're going to put in the risky asset and what fraction you're going to put in the safe asset. Done? All right. So now, in order to compute what the returns would be, well, you need to know how many dollars you have in you're going to allocate now to the risky asset and how many dollars are you're going to allocate at this iteration to the safe asset. Well, how do you do that? Well, it's very simple because I know what risky allocation. How much money am I going to put in the risky asset? Well, I know my account value and I know my weight then I'm going to put in it. So that's it. My risky allocation is nothing more than the fraction of the account value that I should be putting in the risky asset, and my safe allocation is the exact same thing, except it's the safe way. Yeah. Good. So now, I know how much money I put in the risky asset, I know how much money I put in the safe asset. Well, we're ready, we're done. Now, we have to compute, we have to actually invest that money and see what the new account value is. So I'm going to update the account value for this timestep. How do I update the account value? Well, the account value is what? It's the sum of two things, whatever I have in the risky asset plus whatever I have in the safe asset multiplied by the returns of the risky asset. So it is nothing more than the risky, it's whatever I allocated to the risky asset, that's how much I allocated with those. I have to multiply that by the return of the risky asset at that timestep. We have the set of risky returns already. So it's risky are and we know that, but I want the return for this timestep. So I go.iloc. If you remember.iloc is how you index when you have an integer value, when you have an integer row number. We got that. So we don't need all these parentheses that was just showing you that. So that's the risky allocation. That's the amount that the risky allocation grows by. What do we do for the safe allocation? Exact same thing, except of course you take the amount that you have allocated to the safe asset. So let's say you have $700 here and $300 here, then you take $700 and you multiply that by the return for that month for that timestep of the risky asset, and then you have $300 in the safe asset so you multiply that by the safe return for that timestep. In this example, I know I just prefilled everything with about one 12th of three percent but you could have a situation where you actually have the T-bill rate or you have some other say facet. For example, you could be using tips or something like that. Now, we're done. We have the new account value and I could just stop here and I would in fact have implemented CPPI. But I just want to do one little thing here which is to save the values. So I can look at the history and plot it. So remember I created all these account history DataFrames, I just have to remember to stuff these values into the account history DataFrame. So what is the first thing I want to do? I want to do the cushion history. So for that step, so again iloc step, and I want to save the cushion, so I'll just stashing the value away that's all I'm doing here. What else? I want to keep the risky weight history for this timestep and I want to assign the risky weight here. I want to stash the risky weight away. What is the other thing I was going to do is the account history itself, that's actually the most important one. So I do iloc and I take it for this timestep and what do I do, I just save the account value there. So let's just make sure that we got all of this right. We set up the dates, we fixed that to dates there and then we've got the histories initialized and then for every step in the algorithm in the back-test, what are we going to do? We're going to compute the cushion, we're going to compute the risky weight, the weight that should. So the cushion is the risk budget, that's going to get turned into a weight to allocate to the risky asset, make sure that the risky asset is no more than one, make sure it's no less than zero and then whatever you are not putting the risky asset from the safe asset. So now that you know the fraction of your portfolio that has to be put in the risky asset, you know now what is the allocation, how many dollars have you really allocated to the risky asset, and how many dollars have you allocated to the safe asset. Now that you know how many dollars, you can compute the new account value because you just have to grow it by whatever you have by the risky asset times one plus. So it's that times one plus R. So grow the risky allocation by that return and grow the safe allocation by that return. Now that you know your new account value, you can stash away the new account value and for the record also keep the risky weight and also keep the cushion for the step. So that is CPPI. I mean, that's pretty much the entire CPPI algorithm and of course a nice chunk of that is just this stuff just storing values. So you can see the CPPI algorithm at its core is literally something as simple as that. Now look at the account history. Let's just look at the head of the account history. There you go. You see that, if I had run CPPI on still, this is how I would have done, if I'd run CPPI on finance, etc. So I think we can, let's look at it one at a time. So let's look at, let's say account history for let's say beer, and I'm going to plot that. What am I going to do is, you know what I'm going to do. Let me also compute something that I'm going to call the risky wealth. So let's call that risky wealth. Am just using that as a comparison. The risky wealth is nothing more than what I would have got if I had not done any of the CPPI stuff. If I just stock my money in the risky asset and not done any of the CPPI stuff. So how do I compute the risky wealth? Well, it's very easy. It's just what we've already done several times before. It's whatever the starting value is and then you multiply that by the one plus risky return, and then you do the prod on that. All I'm doing is, this is the compounding of a dollar and then I'm multiplying that by the start value. Then all I'm doing is compounding it, and sorry the Kim product that.