All right. Welcome back. This is the real deal, all right. So let's jump right in. We've already done the two asset, let's do the real thing now, okay. So I'm just going to do the usual stuff that by now you're tired of seeing me do, pulling the data and generate the expected returns vector and the covariance matrix for that period. Okay. So that's good. Let's think about what we did last time. We basically created a two asset plot. So I'm going to actually start with exactly that, and of course, we're not really going to be able to use that almost identically, but let's use that as a starting point. So here's the way I want you to think about this. Okay. Let's format it for Python. Okay. So here's what the basic idea is, all right. So I'm not going to do a two asset but only a very small number of things are going to change, but it's an important fundamental change. So of course, it's not going to be a two asset and it's going to say I'm going to plot the N-asset version, okay. Now, the interesting thing is, most of the input here changes, the only difference is expected returns vector and a covariance matrix that is not adjust two assets. So I'm going to take that error out. So I'm actually take that code out. Now, the weights is going to be the tricky part. In this case, the weights were super easy. The weights were just linearly spaced because the moment I peg one weight to the other weight, was done. That is not going to work anymore. So this is the big question mark. This is going to stay the same, because once you've figured out what these weights are on the edge of that frontier, the returns, the volatilities, all the stuff is going to say exactly the same. So that's really the point I wanted to make is that all I'm doing here is I'm going to make one change, but this is a major change and we have to get a fair amount of machinery to work to be able to do just this part. But I'm just going do that so you can look at it and think about this as a very one line change. Of course, it's not a one line change, it's a much more complicated change than a one line change, okay. How do I generate that weight? Well, I have to find the portfolio that gives me the minimum volatility. So I'm going to say minimize. Lets do that minimize vol. But for what? For a certain target return. All right. The whole question is how do I do this? Okay, good. So let's get to it. It's a fair amount of work that we have to do here. One of the things that I'm going to have to help you get comfortable with is optimization because what this is is nothing more than an optimization. This is where that quadratic optimizer that we talked about in class is going to kick in. So let's talk about that. So the quadratic optimizer that want to use is a built into SciPy. So I'm going to say from scipy.optimize, import, minimize. Okay. So let's think about what we're going to try and minimize. Let's start by just reproducing what we had before. All right. Just do it all over again. ''Games'' and ''Fin.'' I think was what we use. It doesn't matter if that isn't the exact one. What I'm going to do is erk.plot_ef2. Let's say 20 points er for those expected returns for those assets, and the covariance matrix for those assets. All right. Let's just plot that and see what we get. Okay. So why did I say this? What I'm going to do now is I know the lowest possible return, I know the highest possible return, so now I'm going to stick some number in between. Let's pick one. Let's say that 15 percent. What I'm going to try and do is I'm going to say, how can I figure out the weights of the portfolio that gives me that 15 percent return, but gives me the lowest possible volatility? Well, that's blue point there. So that's going to be our goal. If we can do that for a whole range of target returns, starting from here all the way to the end, then I can do that for all the vectors. Okay, every single vector along the way, weight vector. When I say all the vectors, I mean all the weight vectors. Okay, so that's the game plan.All right. That's the game plan. So let's figure out how to do that. This is going to be the tricky part. Okay. So let's write our function. So I'm going to define my function called minimize volatility. What is it going to do? I'm going to give it a target return. I'm going to say, ''I need you to give me this much return,'' and you give it the expected returns and the covariance matrix, of course as usual. What do we want it to do? We want it to say, "You want to go from target return to a weight vector." All right? Good. So how are we going to do this? The first order of business is to figure out how many assets we got. All right. So let's say n is the number of assets. How do I do that? I can pull that from for example, this weight. So the expected return vector obviously has many rows as I have assets, so that's easy. Now, the way the optimizer works is you need to give it an objective function, you need to give it some constraints, you need to give it an initial guess. So let's start with the easiest of them all, which is the initial guess. So let's say init_guess, and you can give it almost any initial guess, the optimizer is pretty good. Some people use the initial guess of putting all of the weight in the first record. I like equally weighted. So I'm just going to do this np.repeat. I don't know if I did this, so I'm just going to import NumPy as Np, just so it doesn't yell at me. Okay. I may have done it already, but okay there you go. So what I'm I going to do? I'm going to repeat one by n, n times. Okay, so that's my initial guess. Good, I'm done with my initial guess. Now I have to give it the constraints. The first constraint that I have to give it is, I want every weight to have some balance. I don't want to have crazy weights that are more than one because that's the equivalent of leverage, and I don't want negative weights. That's the quote of going short. So I want to constrain this problem. So let's give it some bounds. The way you give this SciPy optimizer, we're using this guy here, scipy.optimize, this minimizer. The way we give it is you have to give it a sequence of bounds for every weight. Because remember, the result of this is going to be a weight vector. So what you do is for every element of the weight vector, you have to give it the bounds. For us, the bounds are really simple. It's between zero and one, but we need n of those bounds. So here's how I'm going do this. I'm going to generate a tuple. It's actually an interesting way to do it. So that's the lower bound of the tuple. So I want a minimum of zero. I don't want you to go any less than zero in any asset. I want you to go 100 percent as the maximum. So that is the absolute maximum. So that's a tuple. Right there, that's a tuple, a pair. But I want to give a tuple of tuples, and that's what I'm going to do here. So that is a tuple that just has a single tuple in it. All right. Make sense? So the outer parenthesis and that comma says, "It's a tuple." See if I left this out, this would just be brackets. This would just be parenthesis. So it's just a single tuple. I want a tuple of tuples, so that's why I do that, okay. Now, I want that tuple to re repeated for every asset. So all I do is multiply by n. We did this way back with lists. I don't know if you remember that, but this is essentially multiplying a tuple or a list just makes n copies of it. So it's just a really nice short-form way of doing that. So what is this bounce? So in fact, let me do this. I'm going to just for fun because I know that you're dying to see this. Let's open up an extra console here, and I'm just going to do exactly that. So let's do 1, 0, that's a tuple. No surprise. If I did this, all I get is the same tuple again. It's 1, 0 again. But if I did this, it's a tuple of tuples, and if I did this and say multiplied by five, I get five copies of that. Just to flog a dead horse as they say. Let's say 1, 5 is my list. Let's make it more interesting. ''hello'', ''there'' is my list. Okay. Now, if I say "hello'', ''there" I can even do five times ''hello'', ''there.'' All right. It just replicates it. It just makes copies. All right. Well, that was just a detour. Let's go back to where we were before. Let me turn off that. So I can go here and I have a kernel running for that console, I don't really need it anymore. I'm going to shut it down. Okay. So that's how you can manage those terminals there. Sorry, manage the kernel there. Okay. In fact, we don't need to see this at all. Okay. So you're done with that. So now, we know what the bounds are. So that's one constraint that we had, but we have the most important constraint perhaps, is that we've got to make sure that the return that we generate from that set of weights is the return of the target. The constraint is what we want to minimize the volatility subject to the constraint that the returns are whatever that 14 percent or 15 percent of what it is we've got here. I'm going to run it actually in a second with 15 percent, but let's try that. Okay. So what I'm going to do is I'm going to define something called return_is_target, and return is totaled. Let me increase the font size a little bit here. Then, returned is target, is what? I have to somehow tell it that, "hey, we have to make sure that whatever weights you come up with, whatever weights the optimizer comes up with has to satisfy this constraint", and the way you supply this constraint is in the following way. The first thing is you tell it what type of constraint it is. Here, this one is a type of constraint which is an equality constraint. We want the following thing to be equal. What do we want the following thing? What do we want the width to be? What we want is, we're going to have you call a function, Mr optimizer. The optimizer is going to call a function, sorry, fun is what it's called, and the function,right, it's going to be a function of the weights. So what is going to happen is, the optimizer is going to generate something, is going to generate some weights, and then it's going to say, "Hey, does this work for you? Does this meet your return is target constraint?" So what I need to do is now write a function that basically says whether the constraint is met or not. The way I'm going to do this is something that might be foreign to you, so bear with me. Okay. So what is this function? This function is going to get called, and we're going to say that it satisfies the constraint, if the return value for that function equals zero. So let's say target is met, is the name of this function that is going to get called, I don't have any such function called target is met, so let me just write one here. So I'm going to say "def target is met". Target is met is a function of what? First, of course, it'll be a function of the weights, and I'm just going to say W here for a second, and then of course, I can't tell what the target is met or not. So let's think about what that is. If the target is met is going to return zero, if the portfolio with those weights has a target return equal to that target return. Right. So how do I do that? So the way I do that, is I have to say that target is met is a function of what?, to be able to tell whether the target is met or not, I need the expected returns. What that's going to do is it's going to return what, it's going to be zero if the target is met. What does I mean? I've got to first compute the portfolio weights. So I say erk.portfolio return. This is the function that we've already written, and so when you give it those weights W, and you give it those expected returns er, it's going to give you back some returns. That return had better be the target return. So what I'm going to say is target return minus this had better be equal to 0. That's the basic idea. But, I'm going to show you a cool little trick. This is the basic idea. But I'm actually not going to define a function and all this stuff because this function is really just existing for the purpose of meeting this need here. You can do something called an anonymous function. It's just a function that gets defined on the fly. It's called a Lambda function. So, exactly what is going on there, I'm just going to write it slightly different. In instead of defining a function and coming up a name for it, I'm just going to do it right here on the line right there. So it's Lambda, it's called a Lambda function because it's an anonymous function, and what this function do, it takes two arguments, weights and expected returns. What does it do? It returns, its a very short-form way of saying this, is exactly what I have there, target return minus all that stuff, except that I should call it weights. So far so good, yeah. That's squished down the sides a little bit. Okay. Thank you. That's better. Okay. So this is critical here, this is how that constraint has set. The constraint says the return is the target, that return target is met. It should equal to zero is what that saying, and the way that the optimizer determines whether the target is met, is that it's going to call this function, and the function is going to be given the weights and er, so we have to somehow tell this optimizer. By the way, I can't tell you if the return is met or not unless you also give me the er, so I'm going to fix that in a second. And the result is going to be zero, when is it going to be zero? If the target return minus the er minus the portfolio return for those weights is zero, that means in other words, these two are equal. In outwards, the portfolio returns for those weights is equal to the target return. So to be able to do this, we just have one little detail that I've left out, and that is I have to tell the optimizer, that "hey, do me a favor and make sure that you send me the args", these additional arguments. The additional arguments here are again a tuple, and I'm just going to say cov. So I need the expected returns. We're going to use the covariance matrix in a minute. All right. So type is equality, additional arguments that you need to send to the function are the expected returns, and the function will get called with the weights and the additional arguments, that's what that is, and the way that function behaves is it says it's going to be equal to zero when the constraint is met. All right. So I warn you there's a lot of heavy lift in to be done in the section. We're almost done though. There's one more constraint that I want to get in, and that is that the weights sum to one. Let's call it weights sum to one. So I have to make sure that the weights sum to one, again that's an equality, so this should be very familiar now. The type is an equality constraint. What is the function? I don't need any additional arguments, I can do it with just the weights, you don't need to tell me anything more than the weights. So it's Lambda weights. What is it? np.sum of the weights minus 1. So that thing should be equal to zero. That means the constraint is met. So by the way, I can get her all this stuff here now. I don't need this. Why? Because I have a lambda function that does exactly the same thing. All right. So we've got the constraints. What are the constraints? We've said that the bounds of each weight must be between zero and one. We have said that the return must in fact be whatever the return retargeting. We've also said that the weights should all sum to one, and we're ready to now actually call the optimizer at this point. Whether the indentation, yeah, that's because of this. Okay. Now, we're ready to actually run the optimizer. So what does the optimizer going to do? It's actually going to generate a set of weights. We've already said that we're going to call this thing, what did we call it? scipy.optimize, and we've imported the minimize optimizer from there. So let's do that. So we'll call the optimizer minimize. So what are the things we have to do? We want to minimize, what do we want to minimize? We want to minimize the volatility. So the this is the objective function. So the objective function we've already written it as a matter of fact. That's basically our good old portfolio of all function. So all we want to do is minimize the volatility of the portfolio that we are trying to build. That's the objective function. You have to give it the init guess. So we've got that. What are the other things? Now, what is it going to do? It's going to call portfolio_vol. But portfolio_vol needs not just the weights. It needs the covariance matrix. So in fact, you can go look at that, and if I do Shift tab, this is the minimization function. But here let me just cheat this little bit. There you go. So you see it means the weights and the covariance matrix. So what do I do? I have to tell the optimizer, I have to tell minimize that, "Hey, I need you to send me these additional arguments." So that's that. Of course, what kind of optimize? This minimize function supports all kinds of optimizers, and the method that we wanted to use is the quadratic programming optimizer, and that is SLSQP, is what it's called. Really nice optimizer, a very good optimizer. There's one annoying thing about it, that it prints a lot of stuff, and I really don't want it to display anything. So I give it this additional option always of display is false. There's no harm if you keep it on, but I find it annoying because it tells me stuff I don't care about for the most part. So I'm almost done. What are the remaining things I have to give it? I have to go with the constraints and the constraints are the ones I've already done constraints are, what did I we call it? I had one called return is target. That's one, and then weights sum to one, that's another one, and that's it. We don't have any other constraints. Then we do have some bounds. So let's do that. Bounds is, we called it I think bounds as well. All right. So when we're done with all of that, the minimization routine is going to find a solution that satisfies the bound, that satisfies the constraints, and run the objective function, minimizes the value of the portfolio volatility, and finds the weights that minimizes the volatility. Actually, let's just do that. Let's call the results, because it returns all other things and we want the actual set of weights. This is the actual weights that satisfy this, and that's what we want to return, so you can do results.x. It passes it back in a variable collects in that structure. Okay, wow. That was some serious heavy lifting. Let's hope all this worked. Well, the first thing I want to do is see if it worked. So it's actually pretty easy to first to see if it works. Why? Because we do know that at least for the two-asset case, we actually know what the proper answer is. If we give it the two-asset case, we should be able to figure out if we get that answer right there. So let's try that. Let's give it the two-asset case as our starting point. So I don't remember what we have as L. Yeah Games Fin, that's good. So we already plotted that. Let's try it with 15 percent, and I'm going to call that, what is that? That's 57 that's probably 56. So something in the range of 0.056 is what the minimum weight for 15 percent return is for a portfolio that consists of just these two assets. You can't go any lower. You cannot build a portfolio any lower that has 15 percent return and those assets. So let's try this. So let's say w15. It's just what I'm going to call it, these weights. So I'm going to call my optimizer, minimize_ vol is what I called it, 0.15. Now, I have to give it expected returns. So just remember we're calling this function. So we were going to target that return and you have to give it expected returns and co-variance matrix, and let's do. Cov.loc [l, l]. So we're going to get some set of weights. I don't know what they are. Let's see if the volatility of that portfolio, so I'm going to call it 15 portfolio is erk.portfolio. Because we know if you give me the weights, I can always figure out what the volatility of that portfolio is as long as I give it the covariance. So far so good. Let's look at vol15. It's going to chug away, did not work. Of course that's always the fun of coding live. Well, why did it not work? It says local variable weights sum to one reference before assignment. That doesn't make any sense because we definitely assigned to two weights. Maybe we didn't. We did not assign to it. Okay. Let's try this again. We redefine that, and I'm going to run this optimizer again, and let's see if it works any better this time. That was pretty quick. So you've got an answer and the volatility of that portfolio. So let's think about what happened. I told it, "Hey, find me the portfolio that has a target return of 15 percent given these expected returns in this covariance matrix, and give me some weights." So let's look at what those weights were, w15. So it determine that if you had put 47 percent in the first asset and 52 percent in the other asset, you would end up with a portfolio that gave you the minimum possible volatility for a target return of 15 percent. So let's look at that curve, and what that's saying is right there, 15 percent. There is no way to use those two assets and get a portfolio volatility that is less than this number. That number is about 0.056, let's call it, and hopefully that's the number we got 0.56. Perfect. So our optimizer is working beautifully. So what I'm going to do before I jinx it is I'm going to take all that code, and I'm going to put that in my edit brisket. So I built a portfolio optimizer, at least a minimum volatility optimizer, and I'm going to save it. Again, just to be paranoid, I'm going to go in here and make sure that I get the exact same returns. If I say erk.minimize volatility, yeah, did not. That didn't work because it said minimizes not defined. Of course it's not defined because I only defined it here, I didn't define it in that file. I didn't do that. So let's do that. Let's fix that. I'll go in here and put that in here. I really hope that these errors are not annoying to you and that they are actually helpful because I actually try not to edit these out, because these are things you're going to hit and being able to see these errors and being able to figure out what's going on and watch me do it live, I think is actually useful. Okay. So let's try that one more time. Do that one more time. So we go in there and we say, now maybe it actually is getting annoying. So let me fix that. It's telling you that erk is not defined. Of course, erk is not defined because it's in erk. So let's fix that. So I apologize. But again, I promise you there is some benefit to this. So we're going to do that. So you understand what's going on here. You don't have to call erk anymore because portfolio, you're in erk. So portfolio_ vol and portfolio_ return are functions that are available right there within that module. You're already in the module. So I'm going to save that, and I really hope it works this time, and there you go. We got the same return, and the volatility is 15 and we got the same instead of weights back. Okay. So we've now figured out how to minimize the volatility. We're still not quite done, we're almost done, but we have one little detail, which is we've got to actually plot all those points. So if you give me a target return, I can tell you what the weights are. From the weights of course we know how to get to the x and y coordinates. That is the return and volatility coordinates. All we have to do now is generate a grid. Okay. So what do we do now? Well, let's go back to our plot_ef thing, and remember I had said that we got to fill this out. Thus everything else is exactly what ef2 was, we just have to do this part. So we'd said it's not yet implemented well guess what, its going to get implemented right now. All right. So all we have to do now is generate a sequence of weights. So let's do exactly that, by defining that function. It's going to turn out to be very disappointingly simple, because we've already done all the heavy lifting. So let's define this function, def optimal_weights, and it's going to take these three things. What does it do? It generates a list of weights to run the optimizer on. Okay. All right. How are we going to do this? What does that function look like? Well, we've already done all the hard work. What do we have to do? So all we need to do is generate a set, sorry it's not a list of weights and stuff. Okay. So how do we do this? So all we have to do is generate a list of target returns, and send that to the optimizer that we already know knows how to generate weights from target returns. We've already done that part. So let's do this. So all we have to do is generate the target returns and we know how to do that because we already did that in an earlier case. What does that? It's just np.linspace. So all we're doing is just linearly spaced points from what? ER.min is the return of the portfolio with the lowest return, and Er.max is the return the portfolio with the highest return, and we want to have end points between the two of those. So that's it. We're done. That's the set of target returns that will going to now hit and target. I go to target returns, I need to convert that to a set of weights. Well, we already wrote an optimizer that does exactly that. But it does it with one target return, so we want a series of weights from a series of target returns, but we already know how to do that too, why? Because we did this thing called a list comprehension. So we want a list of weights. So what is each element of this? We'll each element of this is exactly what that minimize_vol function does. We just wrote it. So let's do that minimize_vol, and this time we're going to give it a target return, and you, of course, have to give it the expected returns and the co-variance matrix, for what? For target_return in target_returns, target_rs. Okay. So all that's saying, that's a fancy way of saying, loop through every one of these, run the optimizer on every one of those, and give me back the set of weights. Okay. It's just a more compact way of saying exactly the same thing, and I'm done. Yeah. So generate the target returns as a just points between the minimum and the maximum target return, that's what that says, and then convert every one of those target returns into a set of weights using my minimize volatility, and then plot it. Let's see if this works. All right. So let's try it now on a four asset portfolio. So now, my returns not just a two-asset portfolio, I'm going to go "Smoke", what else can I fin, of course, never want to leave that guy out. Let's play some games, and let's invest in coal. That's my list of assets. Now, I'm going to call my plot_ef, and what is plot_ef need? It needs a number of points, let's say 25 points, and let's send it the expected returns for that subset and let's send it the co-variance matrix for that subset. All right. Fingers crossed. All right. Yeah. Well, sure, because the portfolio return and portfolio volatility I haven't got, so let me just do erk here, because I haven't got it here, erk, and when I do copy it, I'll have to remember, take that out, but let's try that again, that didn't work either, pd is not defined, of course, because I only imported numpy and not pd. Import pandas as pd, okay, and let's try that one more time, and okay, four asset, multi-asset Efficient Frontier, done. So let's do this, let's take all this code that we've written, and put it away in our edhec_risk_kit. This time, I'm not going to make the mistake that I made last time. So let's copy that in. I don't need this, because I've done that right on the top, and let me just remember to take that erk stuff out. Okay. All right. Save it. Go back to my portfolio optimizer thing and see if I can get the exact same by doing this. Fingers crossed, hope for the best, and there you go. We have added efficient frontier plotting computing function in our toolkit and that seems like a really good place to stop, but that doesn't mean you should stop, I would encourage you to play around with this stuff. Try and break it, try and understand exactly how it's working, stare at the code for a little while, and maybe make some changes. See you then.