I Rebuilt Edward Thorp's $800M Pairs-Trading Strategy for 2025 (Full Python Walkthrough) — Transcript
Full transcript
- 0:00Let's talk about Paris rating again. It
- 0:01is the main strategy used by Edward Torp
- 0:04to make his 800 million fortune. Now, if
- 0:06you don't know who that is, he's the guy
- 0:08who invented card counting and proved
- 0:10that blackjack can be beaten
- 0:11mathematically. He also invented the
- 0:13first variable computer, but not to see
- 0:15time or anything like that, but to beat
- 0:17the roulette game. He tried it secretly
- 0:19in Las Vegas using his physicist buddy
- 0:22Claude Shannon. Now, if you don't know
- 0:23who that is, he's the father of
- 0:25information theory, and together they
- 0:27literally hacked the roulette game. He
- 0:28also invented quantitative trading with
- 0:30his most dominant strategy being pair
- 0:32trading or as he usually called it
- 0:34statistical arbitrage which is a market
- 0:36neutral strategy meaning that you're
- 0:38going to make money whether the price
- 0:39goes up down or whatever because at each
- 0:41point we're going to have one long and
- 0:43short position simultaneously so the
- 0:45trend of the market is not what we are
- 0:46looking for now if you want to know more
- 0:48about Edward Torp himself I suggest
- 0:50reading his book a man for all markets
- 0:52in this video I will go through the
- 0:54basics of persing very quickly so if you
- 0:56haven't watched my previous video about
- 0:57this topic Don't worry about it. But if
- 0:59you have, don't skip this video because
- 1:01in this one, I'm going to extend where I
- 1:02left the previous video and share with
- 1:04you the experience that I had running
- 1:06this strategy for a while. And before we
- 1:08continue, I got to say I am not a
- 1:09financial adviser and this video is
- 1:11merely a tutorial. All right, let's get
- 1:12to it.
- 1:14To show you the basics, I'm going to use
- 1:16a Jupyter notebook, which if you want to
- 1:18run it yourself, feel free to copy the
- 1:19code and run it inside a Jupyter
- 1:21notebook. But since we are using the
- 1:22Jesse framework in order to import the
- 1:24data and some other stuff, you need to
- 1:26ensure that this notebook is inside the
- 1:28root of your Jessie project. Now, if
- 1:30you're wondering what your project is in
- 1:31Jesse, well, it's the one that has the
- 1:33folders of strategies and storage. And
- 1:35here, as you can see, is my Jupyter
- 1:37notebook. Now in it I am simply
- 1:38importing the research module of Jesse
- 1:40and its utility functions which are
- 1:42really helpful and you're going to see
- 1:43it in a second and its helpers and the
- 1:46numpy library the daytime and the math
- 1:48plot lib so I can print out some
- 1:50beautiful charts for you guys and you
- 1:52need to ensure to choose the correct
- 1:54cond environment where Jesse itself is
- 1:56installed and for me this is the path to
- 1:58that one so now I'm able to run this now
- 2:01next I'm going to choose the binance
- 2:03perpetual futures as the exchange
- 2:05because in order to go long and short
- 2:07simultaneously. You cannot do that on a
- 2:09spot market, right? So, you need a
- 2:10perpetual futures market. As for the
- 2:12start date, I'm picking the first day of
- 2:152025 and the finish date is the first
- 2:17day of September. And then I'm using the
- 2:19research modules get candles function of
- 2:21Jesse to get the candles. Here's the
- 2:23exchange, the symbol, and the time
- 2:25frame. I'm using 50 minutes. This, by
- 2:27the way, could be anything. So, you just
- 2:29need to play around with it and find the
- 2:31correct time frame for your strategy.
- 2:33Obviously, the lower the time frame,
- 2:34it's going to take more trades. So you
- 2:36will be catching more opportunities, but
- 2:38as a result, you will be taking more
- 2:40trades and end up paying more trading
- 2:42fees. So that's the downside of choosing
- 2:44a lower time frame. By the way, the
- 2:46reason I'm using an underscore here is
- 2:48because I'm telling Python, hey, drop
- 2:50the first value that you're getting from
- 2:51this function here. And I'm only
- 2:53interested in the second one because the
- 2:55first one is the warm-up candles, which
- 2:56we don't really need it in this Jupyter
- 2:58notebook. But if you are executing some
- 3:01actual back test with it, you're going
- 3:02to need it. But anyways, just forget
- 3:04about it and use it as this because
- 3:06we're going to run the actual back test
- 3:07inside DJs framework itself and we don't
- 3:09need to know about these stuff and it
- 3:11will take care of them behind the scenes
- 3:12for us. All right, so let's run this.
- 3:15And as you can see, it's going to take a
- 3:16while and it's done. Next, I made an
- 3:19array of times in order to be able to
- 3:21show this on a chart. And then I'm using
- 3:23the utility function of Jesse called
- 3:25prices to returns to convert the prices
- 3:27into returns. And the reason I'm doing
- 3:29that is you see we're using Ethereum and
- 3:31Ethereum Classic for this example. And
- 3:32Ethereum itself has a price ranges of
- 3:35thousands of dollars, but Ethereum
- 3:36Classic has prices in hundreds of
- 3:38dollars. So in order to be able to do
- 3:40the calculations, we need them both to
- 3:42be inside the same range. And to do
- 3:44that, we're going to convert the prices
- 3:45into returns. And next, I'm simply
- 3:47creating a chart using math plot li. So
- 3:50let's see how this looks like. All
- 3:51right. So you see they both begin from
- 3:53the same point, which makes sense. And
- 3:55at some point Ethereum Classic which is
- 3:57this red line here by the way you can
- 3:58see in here is above the price of
- 4:01Ethereum itself and sometimes it's below
- 4:03this one. So in simple words what that
- 4:05means is that if I were to go long
- 4:08Ethereum Classic here and short Ethereum
- 4:10itself and then at this point if I were
- 4:12to close that position I would have made
- 4:14money and I could have done the
- 4:16opposite. So here I could have gone
- 4:17short on Ethereum Classic and long on
- 4:19Ethereum itself and then at another
- 4:21point such as here I could have closed
- 4:22that position. And there's been so many
- 4:24other opportunities in here, right? So
- 4:26you see that's what I meant when I said
- 4:28we don't really care about the trend
- 4:29like it doesn't matter if where the
- 4:31actual price is going because in reality
- 4:33yes both Ethereum and Ethereum classic
- 4:35move together they are correlated right
- 4:37so they either move up together or down
- 4:39together but not exactly right so it's
- 4:41not right to the point and those are the
- 4:43times that we want to use as our edge in
- 4:46order to make money so the question is
- 4:48how do we exactly measure this so this
- 4:50difference that I'm seeing on the chart
- 4:51and I explained it right now how do we
- 4:53measure it with actual numbers so we can
- 4:55write the code to do it automatically
- 4:57for us. Now, to do that, we're going to
- 4:58need something called zcore, which is
- 5:00not really that complicated. So, it
- 5:01basically needs how much further the
- 5:03price is moving away from the mean of
- 5:05itself. Now, first we calculate the
- 5:07spread, which is the difference between
- 5:08the two price ranges and then we
- 5:10calculate the zcore. And Jesse already
- 5:12gives us a utility function for that.
- 5:13But if you are interested about the
- 5:15formula of that, so here it is. So, the
- 5:17price ranges minus the mean of that
- 5:19series divided by a standard deviation
- 5:21of the same series. So, it's nothing
- 5:22fancy, but very useful. Then we get the
- 5:24mean using numpai's mean function. And
- 5:27now if we plot it again. So you see the
- 5:29blue line here is the zcore. And this
- 5:31red and green lines are simply two
- 5:34thresholds that we are defining. So I
- 5:36have used the numbers 1.2. You could
- 5:38choose whatever you want. But the lower
- 5:40the threshold number, the more trades
- 5:42you're going to take. All right. So this
- 5:43is very simple. Basically we want to say
- 5:45that whenever the zore goes above this
- 5:48red threshold, we want to go short the
- 5:50first asset and long the other one. And
- 5:51then we close it whenever it reaches the
- 5:53mean which is this black line here. And
- 5:55whenever it goes below the minus 1.2
- 5:58threshold line we want to go along the
- 5:59first asset and short the other one. Now
- 6:01we can also check statistically to
- 6:03ensure the two assets are in fact
- 6:06because we don't just want them to be
- 6:07correlated which is they are moving
- 6:09together. We also want to make sure they
- 6:10are conjugated. Now I'm super
- 6:12simplifying these terms. So if you want
- 6:13to know about them make sure to look
- 6:15them up. But since Jess is providing
- 6:16these functions for us it's not going to
- 6:18be that hard to calculate it. And if I
- 6:19run this, you can see the result which
- 6:22is it's saying yes, it is going
- 6:23integrated. So these two assets are fine
- 6:25to trade. But maybe the assets that you
- 6:27want to trade are not so obvious like
- 6:28Ethereum and Ethereum class. Maybe you
- 6:30want to run it on two assets such as the
- 6:32token of two different exchanges such as
- 6:34DYDX and let's say the hype token.
- 6:36Before running it, you want to ensure
- 6:38they are indeed integrated. So for that
- 6:40you can use this utility function. All
- 6:42right. So there's one issue. If I were
- 6:44to write the strategy and run it with
- 6:46these exact numbers, I would have fall
- 6:48into the look bias. What that means is
- 6:50that because we already have all the
- 6:52data and we are calculating the mean for
- 6:54the entire data and then we are choosing
- 6:56these threshold lines such as 1.2, we
- 6:59are using data from the future. So if
- 7:01you execute the strategy with this,
- 7:03we're going to see good numbers. But
- 7:04when you actually run the same strategy,
- 7:06it is not going to perform as well. So
- 7:08to fix that, we need to ensure that we
- 7:10only use the data that we already have.
- 7:11But one of the good things about JC
- 7:13framework is that when you use the data
- 7:14inside the strategy, you will only have
- 7:16access to the data so far. That means
- 7:18you will never have access to the future
- 7:20data. So you don't really need to worry
- 7:21about this. But I just needed to mention
- 7:23this because I know some of you who know
- 7:25the math better than me are now thinking
- 7:27oh what this guy just did is the look at
- 7:29bias. Now here's a previous video that I
- 7:30made about this topic and in the
- 7:32description section of the video I was
- 7:34linking to this GitHub repository. Now
- 7:36in it we simply have two strategies
- 7:37press rating and press trading two. So
- 7:39press rating two is actually simpler. So
- 7:41let me show you that first. All right.
- 7:43So you see this strategy is simply
- 7:45following whatever logic the first
- 7:46strategy is giving. So it's reading the
- 7:48values that have been set by the first
- 7:50strategies using the self.shart vs
- 7:53dictionary of Jesse to know whichever
- 7:55position should go long or short or
- 7:58whether or not it is time for it to
- 7:59close or liquidate the current position.
- 8:01So it's pretty simple and in fact we
- 8:03don't need to change this code like at
- 8:05all. So instead let's go to the first
- 8:07strategy and check out the source code.
- 8:08So let's make this a little bigger. All
- 8:10right. So first we have the typical
- 8:12imports of just a strategy. So we're
- 8:13importing the main strategy class, the
- 8:16indicators package and the utility
- 8:18functions and we simply define the pair
- 8:20rating class and it is inheriting from
- 8:22the main strategy class. So this is 101
- 8:24strategy in Jesse and in fact when you
- 8:26generate a strategy using the dashboard
- 8:28it will generate these things for you.
- 8:29Now since we were doing our calculations
- 8:31using the price returns and not even
- 8:33price data let alone to something like
- 8:35candle 6. That is why in here we also
- 8:38need to convert the candle data into
- 8:40price returns. So we're using the
- 8:42utility function of Jesse called prices
- 8:44returns. And in it we're using the get
- 8:46candles function of Jesse to fetch the
- 8:48current data for our trading time frame
- 8:51symbol and exchange. And we're selecting
- 8:52only the closing data. And then we're
- 8:54picking the last 200 candles. And this
- 8:57is expected right because if we want to
- 8:58do some calculation based on the past
- 9:00existing data we need some kind of look
- 9:03back period, right? And in this strategy
- 9:05I chose the number 200. You can test
- 9:07your own number to see whichever works
- 9:08best for you. And next, we did the same
- 9:10thing except using the other symbol. So,
- 9:12by the way, I could have hardcoded this.
- 9:14So, for example, this could have been
- 9:16ETH and this one could have been etc.,
- 9:18but I use the self.oute array of JC,
- 9:21which will give me basically a list of
- 9:22the routes that I'm trading. And because
- 9:24in this case, I'm supposedly trading two
- 9:26symbols. So, let's say ETH and ETC, then
- 9:29I know that the first one is going to be
- 9:30ETH and the second is going to be ETC.
- 9:32Next, we calculate the Zcore very
- 9:34simply. So we get the spread by
- 9:36subtracting C1 minus C2 and I'm
- 9:39selecting since index of one until the
- 9:41end because the first value is going to
- 9:43be a non value which is going to mess
- 9:45our calculation. So we need to escape
- 9:46that one and then we use the Zcore which
- 9:48simply accepts the spread and then I'm
- 9:51returning the last value of the Zcore
- 9:53because that's really the one that I
- 9:54care for. So you remember we were
- 9:56calculating Zcore in the Jupyter
- 9:58notebook as well and we were plotting it
- 9:59here, right? So here's the thing. The
- 10:01only value that we actually care for is
- 10:03the last one because if that one for
- 10:05example is below this threshold, we want
- 10:07to go long. If it's here, we want to go
- 10:08short. Right? So we only care about the
- 10:10last value. And that's why in here I'm
- 10:12also selecting only the last value. Now
- 10:14here's where actually the Jesse
- 10:16framework comes in. So we're using the
- 10:18before method which is a built-in method
- 10:20of Jesse. What it does is that it will
- 10:22get executed before every single candle
- 10:24closes. What it does is that right after
- 10:26a candle closes when you're trading this
- 10:29function is the first method that will
- 10:31get executed which is a great place to
- 10:33put some extra calculations that you
- 10:35want to do so that later you're going to
- 10:37use to decide whether or not you want to
- 10:39open a position or close an existing
- 10:40one. Now I'm saying that if it's the
- 10:42first index, so if I have just started
- 10:44my session, we need to fill in these
- 10:46values, right? So S1 position and S2
- 10:48position. So what these two variables
- 10:50are going to do for me is to decide at
- 10:52each point whether the position should
- 10:53be long, short or closed. Now zero is
- 10:56pointing to closed and once every 24
- 10:58hours and this is how I'm calculating
- 11:00that, right? So I'm saying if the index
- 11:02is zero or if the remaining of the
- 11:04division by this equals to zero, I want
- 11:07to check to ensure that the symbols that
- 11:10I'm trading are still integrated.
- 11:12Practically I never use this, but if
- 11:14you're doing some research and you're
- 11:15not even sure if your symbols are
- 11:17integrated in the first place or not, I
- 11:19think it's best to include this part of
- 11:20the code, but otherwise like you can
- 11:22simply just delete these and nothing
- 11:25will really happen. But anyway, so we're
- 11:27using the Rte integrated utility
- 11:28function of JC. So we're not even
- 11:30calculating this by ourselves and we're
- 11:32saying if they are not integrated then
- 11:34simply put the positions to zero which
- 11:36which in other words it means do not
- 11:38open any long or short trade. Next we
- 11:40were getting the zscore and we were
- 11:42saying that if the current position is
- 11:44closed and the zcore is below minus 1.2
- 11:47which is this threshold that we had here
- 11:48the first position must be long the
- 11:50second one should be short and it should
- 11:52also set the position sizing. Now here's
- 11:54a code for that. So we're using the
- 11:56calculate alpha beta utility function of
- 11:58Jesse. We don't really care about the
- 11:59alpha value, but we do need the beta.
- 12:02And we're saying that using the
- 12:03available margin multiplied by these, we
- 12:05are calculating the margin one, which is
- 12:07how much margin we should be spending on
- 12:09the first position and how much we
- 12:11should be spending on the second
- 12:12position. So, I did some research and
- 12:13came up with this formula, but I've seen
- 12:15people using different formulas and I
- 12:17think this is also one of the parts that
- 12:19needs some extra research on our side
- 12:21because the size of the position has a
- 12:23huge impact on the results that you're
- 12:24going to get. And next we're saying that
- 12:26if the current position is a long
- 12:27position. Now by the way when we're
- 12:28saying the current position now we are
- 12:30pointing to ETH in the example that I
- 12:33gave you before. So we're saying that if
- 12:34it's a long position and the zcore is
- 12:36above zero which in this case means that
- 12:38you know here we went long but now this
- 12:41line is above the mean which is this
- 12:43black line or zero in other words then I
- 12:45want to close both positions. And when
- 12:47we set this to zero not just this
- 12:49strategy but every other strategy which
- 12:51in this case is just another one it will
- 12:53also pick up on this. So when it calls
- 12:55the object position function of Jesse,
- 12:57it will see okay, this value has been
- 12:59set to zero and now I need to liquidate
- 13:01the current position. And by the way,
- 13:02this function only gets executed when
- 13:04you do have an open position. That's why
- 13:07in here we don't need to check to see if
- 13:09we have a open position or not. So you
- 13:11see it's little things like this that
- 13:13makes framework really easy to work
- 13:15with. All right. So next we're saying
- 13:16that if it's a short position and the ZS
- 13:18score is below zero, we also want to do
- 13:20the same thing. And if it's closed and
- 13:21the ZS score is above 1.2, to we want to
- 13:24go short with the current position and
- 13:26we want to go long with the other
- 13:28strategy and again we want to set the
- 13:29margin values as well and that's it
- 13:31really like everything else is super
- 13:33easy so next inside the shoot long
- 13:34method which is how we tell JC like
- 13:36whether we should open a long position
- 13:38at this moment or not we're simply
- 13:40checking this value which we set in here
- 13:43right so if it's one we want to go long
- 13:45if it's minus one we want to go short
- 13:48and if it is going long well here's
- 13:50where we do the actual position sizing
- 13:52So we're getting the quantity using the
- 13:54size to quantity utility function of
- 13:56Jesse. But in it, we're not passing the
- 13:58current balance. We're passing this
- 13:59value which we set inside the set proper
- 14:02margin per route function that we
- 14:03defined earlier. And then we're passing
- 14:05the current price and the fee price of
- 14:07the current exchange. And then we're
- 14:08submitting the actual buy order by
- 14:10saying self.by equals quantity and then
- 14:13self.pric. We're doing the opposite for
- 14:15short. And again inside the update
- 14:16position method, we're saying that if
- 14:18the S1 position equals to zero, I want
- 14:20to liquidate the current position. So
- 14:22you see it was super simple. If you
- 14:24remember the previous video, this was
- 14:25the result that we were getting before
- 14:27and it looked fantastic. But the problem
- 14:29was that we weren't paying any trading
- 14:31fees more than 7800 trades. But the
- 14:34numbers were great. Like the max was
- 14:35only minus 5% which is absolutely
- 14:38amazing. Here's the benchmark comparing
- 14:40the equity curve of our strategy to the
- 14:43other trading assets that we were
- 14:44trading which was ETH and ETC. But then
- 14:47after including the trading fees, this
- 14:49was the result that we were getting,
- 14:51which is absolutely horrible. And that
- 14:53was with this much trading fees, which
- 14:54is even a little bit higher than the
- 14:56trading fees that you're going to pay on
- 14:58an exchange such as Binance. So, we were
- 15:00including the trading fees and a little
- 15:01bit of a slippage. And the result was
- 15:03this, which is horrible. But like I said
- 15:05in the intro of the video, in this one,
- 15:07I'm going to extend where I left the
- 15:08previous video, and I will show you a
- 15:10couple of tricks that I picked up, which
- 15:12really improved the result. All right.
- 15:13So, let's go to Jess's dashboard and
- 15:15create a new strategy. And I'm going to
- 15:17call this one first trading 2025. Next,
- 15:21we can continue editing this from within
- 15:23the builtin editor of the dashboard. Or
- 15:26we could copy this, go to another editor
- 15:28such as cursor or VS Code, and look up
- 15:31the name of that strategy and find it
- 15:33here. Now, I find this one more
- 15:34convenient. So, this is the one that I'm
- 15:36going to go with. So, let's go and copy
- 15:38the code that we had before. Paste it
- 15:41here. Uh firstly, let's update the name
- 15:44to include 225 in it. And now I'm going
- 15:48to keep building upon this. So let's
- 15:51copy the name of it. Go to Jess's
- 15:53dashboard. Go to the backlisting page.
- 15:54Look up the name of it. I want to trade
- 15:56this on the 15 minutes time frame. So
- 15:58let's change this to 15 minutes. And I'm
- 16:00going to use BTC and soul trading pairs.
- 16:03Now for the second strategy, I'm using
- 16:05this one, but it is basically the exact
- 16:07same thing that we had as before, except
- 16:10that I just made some updates to it to
- 16:12make things more dynamic. So for
- 16:14example, instead of here saying margin
- 16:162, I'm saying margin and then I'm using
- 16:19this self.curren route index. And I'm
- 16:22doing the same thing in here. And by
- 16:23doing this, I'm ensuring that if I, for
- 16:25example, were to trade not two symbols,
- 16:27but four or six, I wouldn't have to
- 16:29change this strategy to update the
- 16:31number, right? So from 2 to four or six.
- 16:34So I don't have to do that anymore. So
- 16:35that's why I did this. But you could use
- 16:37the previous code or you could use this
- 16:39one. And I will share whatever that I'm
- 16:42writing today on GitHub and link to it
- 16:44in the description of the video. All
- 16:45right. So we picked this one and here it
- 16:47is. For the duration, I'm choosing the
- 16:49first 6 months of 2025. And you need to
- 16:52ensure that fast mode is disabled
- 16:53because when it is enabled, you cannot
- 16:55trade the strategies that use two
- 16:58different trading routes. So, if I
- 17:00enable this and try to run it, it will
- 17:02give me this error. So, I need to
- 17:04disable this. And I'm leaving the
- 17:06benchmark. You know what? Let's also
- 17:07disable this. Let's also check the
- 17:09trading fees. And let's set it to zero
- 17:11first. Now, let's run this. And here's
- 17:13the result that we're getting. So, at
- 17:15this point, it lost a little bit of
- 17:17money, but from here to here, it was
- 17:19making money. And this is how many
- 17:20trades we made. And the maxon is - 41%.
- 17:24So, it's not great even with zero
- 17:26trading fees, right? But that's kind of
- 17:28expected because in the previous video I
- 17:29was using ETH and ETC which are
- 17:32significantly correlated and coins
- 17:34integrate together. But here I'm using
- 17:36BTC and Soul which aren't always going
- 17:39toe to toe, right? But because I want to
- 17:40show you the realistic values, the ones
- 17:42that I actually traded. So let's also
- 17:44bring back the trading fees. So on
- 17:45Binance, this is the actual trading fee
- 17:48that you're going to have to pay for
- 17:50taker or a market order in other words,
- 17:52which is what this strategy is using.
- 17:54But we're going to set it to a little
- 17:55bit higher to consider the slippage
- 17:58value. Now let's run it one more time
- 18:00but this time in a new tab because I
- 18:02want to compare the two values. Now
- 18:04here's what we're getting now. So with
- 18:06the same number of trades now we're
- 18:09losing 95% of the capital that we were
- 18:11trading. The max is - 95%. So it's
- 18:14absolutely horrible and untradeable.
- 18:16Right now this is before and this is
- 18:18after. So now let's extend where we left
- 18:20off in the previous video. All right. So
- 18:22the first thing that I'm going to do is
- 18:24to define a new property and I'm going
- 18:26to call it a spread
- 18:29and just return this right. So I don't
- 18:32want to repeat myself doing this. Now
- 18:34the next thing that I did that worked
- 18:35really well was instead of using the
- 18:37Zcore and some simple hard-coded
- 18:40threshold I started using Ballinger
- 18:42bands. So that means we can simply
- 18:44remove this and I'm going to define this
- 18:46as BB and in it we are using TA.
- 18:50Ballinger bands which is the correct
- 18:52syntax you just see for using the
- 18:54Ballinger bands indicator and instead of
- 18:55passing some candles I'm passing the
- 18:58current spread values which we defined
- 19:00here and as period I'm using 20 and we
- 19:04don't even need to define these. So
- 19:06let's just remove it and we're going to
- 19:07go with the defaults. So that means now
- 19:09I can go to the before function again
- 19:12and here I can remove Zcore and replace
- 19:14it with Ballinger bands. And instead of
- 19:16saying when Zcore is below minus 1.2
- 19:19two, we can say whenever the current
- 19:21spread is below the lower band of the
- 19:24Ballinger bands. So we can say this. So
- 19:26the current spread which we haven't
- 19:29defined yet is below the lower band. So
- 19:31let's also define this. So let's go up
- 19:33here and simply define that. Actually
- 19:36cursor is smart enough to help us with
- 19:37this. So yeah, the current is spread but
- 19:39we're only interested in the last value
- 19:40of it. So we are returning the last
- 19:42value of this. All right. So let's go
- 19:44back and here we can remove this and say
- 19:47whenever the current spread is above the
- 19:50middle band and we also do the same
- 19:52thing here. So whenever the current
- 19:53spread is below the middle band we want
- 19:55to close the position and here we're
- 19:58saying whenever the current spread is
- 19:59above the upper band of the Ballinger
- 20:01bands we want to go short. So if we were
- 20:04looking at it in here so imagine if this
- 20:06line here was the lower band and this
- 20:08one was the upper band of the Ballinger
- 20:09bands right? So we're saying whenever
- 20:11the current spread which is this blue
- 20:13line is above the upper band you want to
- 20:15go short. Whenever it is below it you
- 20:17want to go long and whenever it passes
- 20:19the middle band which is supposedly this
- 20:22black line here you want to close the
- 20:23position. Now again guys this was the Z
- 20:25score. Okay it wasn't exactly the
- 20:27Ballinger bands but the way it works is
- 20:29very similar to Ballinger bands which
- 20:30I'm sure all you guys already know and I
- 20:33got so much better results with this. So
- 20:36again, we defined the boundary bands
- 20:37here and we just use it in these places.
- 20:41So let's go back and rerun this one more
- 20:44time, but I'm not expecting to get like
- 20:46super good results yet because the main
- 20:49ingredient is still left. All right. So
- 20:52even as it is, we're getting better
- 20:53results. You see? So instead of losing,
- 20:56you know, that much, we're losing less.
- 20:58And we are also taking less trades, by
- 21:00the way. So that's also another reason
- 21:02why we're losing less. But overall, I
- 21:04found this model to work better. All
- 21:06right, the next thing that I did that
- 21:07worked really well was using the ADX
- 21:09indicator. Now, in case you don't know
- 21:11what ADX is, so let's display it on
- 21:13trading view. So, here is this average
- 21:15directional index. All right, so what it
- 21:17does is that it tells us how much
- 21:19volatility there is in the market. So,
- 21:21for example, at this point where we were
- 21:22in a freef fall, you see the value was
- 21:24high and in here, for example, where
- 21:27there wasn't really any kind of trend,
- 21:29the value of it was really low. So
- 21:30setting a simple threshold with the ADX
- 21:32indicator helps us a lot to find the
- 21:35moments that the market has a lot of
- 21:36volatility and there's a higher chance
- 21:38of insufficiency in the market and
- 21:40that's what we want to do right we want
- 21:41to find the insufficient moments in the
- 21:43market and take trades and take
- 21:45advantage of those moments. So I simply
- 21:48set a threshold of 30. So on trading
- 21:50view that would be something like this.
- 21:52And I'm saying whenever the value of ADX
- 21:55is below the threshold like in here, I'm
- 21:57not taking any trades. But if it's above
- 21:59it like in here or here or here, then
- 22:02yes, I am looking to take trades. So
- 22:05let's define it here. I'm going to
- 22:07define new property and I will call it
- 22:09ADX.
- 22:12And simply in it, I will say return TA.X
- 22:15X and then I'm passing well this is
- 22:17wrong I want to pass the current trading
- 22:19candles which I can get like this in
- 22:21Jesse and we can also remove the period
- 22:24parameter I will go with the default now
- 22:26I want to say when the value of it is
- 22:28above 30 that's when I want to take the
- 22:30trade so now let's go back here and I
- 22:33will also add this
- 22:36but we don't need it for closing the
- 22:38trade so not here not here but also in
- 22:40here I will add this the next thing that
- 22:42I did was simply defining a trend filter
- 22:45So let's go ahead and do it. So new
- 22:48property I'm going to call it trend. And
- 22:50in it I'm going to use the comma
- 22:53indicator. Feel free to use whichever
- 22:54you like. You can use moving averages or
- 22:56whatever. But I found comma to be very
- 22:59simple and work best for my use case. So
- 23:01I'm going to say k equals ka and then
- 23:04I'm passing the current trading candles.
- 23:07And here I'm saying if the current price
- 23:09is above the k value, it's an uptrend
- 23:12otherwise it's a downtrend. And then in
- 23:14my trading rules, I'm saying that for
- 23:16this one, I want it to be in an uptrend
- 23:19because I want to go long the first
- 23:20position. And in here, I will do the
- 23:24opposite. I will say that if it's a
- 23:26downtrend, in other words, if the trend
- 23:28equals minus one, I want to go short the
- 23:31first position and long the second
- 23:33position. So, let's go back and run this
- 23:35two. You see, it's again improving our
- 23:38results a little bit, but we're still
- 23:40executing 286 trades and the draw down
- 23:44isn't great either. All right, so let's
- 23:45go back and define another filter. Now,
- 23:49this one is the most important filter
- 23:51that I defined. The main problem with
- 23:53the previous results that we were
- 23:54getting was that we were executing too
- 23:57many trades. So, even the trades that
- 23:58weren't really worth it, just because
- 24:00the value of of the ZS score was below
- 24:03threshold, we were taking a trade,
- 24:04right? we weren't checking to see if
- 24:06piano wise the trade was actually worth
- 24:08it. So that's what we're going to do
- 24:09now. So let's define it and I'm going to
- 24:11call it is worth opening and in it I
- 24:13will say return the abstract value of
- 24:17the current spread
- 24:20and you know its last value to be above
- 24:23a certain number and I'm going to use
- 24:261.5%.
- 24:27Now depending on whichever coin you're
- 24:29trading you're going to need to change
- 24:31this. So for example, if you're trading
- 24:32meme coins, the volatility is going to
- 24:34be higher. So maybe you want to catch
- 24:36bigger movements, right? So 1.5% may not
- 24:40be big enough. So for a memecoin, maybe
- 24:41a number such as 4% would be good. But
- 24:44for more liquid coins such as BTC or
- 24:46soul, I'm going with 1.5. And the
- 24:48problem with choosing something below
- 24:50that is that the trading fees of the
- 24:52exchange is going to kill our results
- 24:54and that's just not good enough for me.
- 24:56All right, so let's go here and add this
- 24:58as another inter rule. And I also do the
- 25:01same thing in here. Again, we don't need
- 25:03it in here. Now, let's go back and
- 25:05remember this number. Right. So,
- 25:08actually, let's open a new tab because
- 25:10again, I want to compare the results of
- 25:12all of them. And there we go. So, it
- 25:15drastically improved. Right. So, now
- 25:17we're executing only 28 trades, the ones
- 25:19that are actually worth it.
- 25:22And the win rate is 28%. That's not
- 25:24awesome. The max is - 10%. And if we go
- 25:28to the benchmarking page, we can compare
- 25:30the result here as well. So you see
- 25:32they're all negative still. But we went
- 25:35from minus12 sharp ratio to minus5 to
- 25:38minus2. Now this one was without trading
- 25:41fees. So we don't really need to keep
- 25:43this open at all. Let's close it. Now
- 25:45here's the thing. Just like we are
- 25:46saying is the current trend worth for
- 25:49opening in the first place. We can also
- 25:51say like is it worth to close it?
- 25:53Because why should we always wait until
- 25:55the value goes above the middle band?
- 25:58What if we're not making enough P&L yet?
- 26:00So just like that, we can also define
- 26:02another one. And with this one, we're
- 26:04going to call it is worth closing. Now
- 26:06for this, we're going to have to get the
- 26:09current P&L of the current position. And
- 26:11here's how we get that. So P1 equals the
- 26:13current route, the first one, and then
- 26:16we're selecting the strategy just so we
- 26:18can access its position. And then it's
- 26:19P&L. And we're doing the same thing for
- 26:21the second position. So by the way, this
- 26:23is how you can access the value of the
- 26:25other trading routes that you have from
- 26:27within one single strategy in Jesse,
- 26:29right? So you will use the current
- 26:32routes array and then you have access to
- 26:34all of them. So you pick the strategy
- 26:36and then it position and then it's P&L.
- 26:38So then we're saying the total P&L
- 26:39equals the P1 added by P2. So remember,
- 26:43one of these is probably going to be
- 26:44negative, but when we add them together,
- 26:46that is going to give us like the
- 26:48remaining value. And then we're saying
- 26:49the total P&L divided by the current
- 26:52position's value multiplied by 100. So
- 26:55we can have it in percentage should be
- 26:57above a certain value. And because we
- 26:58chose 1.5% here, we might as well do the
- 27:02same thing in here. So I'm going to
- 27:04change this to 1.5.
- 27:06So we're saying that we only want to
- 27:08close the current position if we are in
- 27:10a profit for more than 1.5%. But I don't
- 27:14want to always do this because what if
- 27:16we are losing money, right? So what if
- 27:18the price is going very much against us
- 27:20and we're losing more and more. We don't
- 27:22want to keep holding the position,
- 27:23right? So for that reason I will also
- 27:25define another one and I'm going to call
- 27:28it time to stop. So this is how we
- 27:29define the stop loss here because in a
- 27:31typical strategy we submit a stop loss
- 27:34as soon as we open the position. But
- 27:35because this is a pair trading strategy,
- 27:37we don't look at just the panel of the
- 27:40current position. We need to add them
- 27:42together like what we did here, right?
- 27:43So at any point in time, we need both
- 27:46values. So that's why it's going to be a
- 27:48little bit tricky and we cannot use this
- 27:50typical stop-loss order. So instead we
- 27:52need to check at each point to see if
- 27:55the current losses is bigger than a
- 27:57certain percentage we're going to close
- 27:58the current position. We're going to
- 28:00liquidate it. Right? So to do that we
- 28:02will define it like this. Very similar
- 28:03to what we just did and we're saying if
- 28:06the current total P&L is below a certain
- 28:09number and I'm choosing minus 5% here.
- 28:12Again feel free to choose whatever
- 28:14number that works best for your use
- 28:16case. If that's the case, I want to
- 28:18close the position. So either close the
- 28:20position when it is time to stop because
- 28:22we are in so much loss or when we are in
- 28:25a good number of profit again. You need
- 28:27to play around with this value and for
- 28:29this pair I found this number to be good
- 28:31enough. So let's go back to our before
- 28:33function. Now this one is for closing.
- 28:36So we no longer update this part. So
- 28:38instead I will do this. So I'm saying
- 28:40and either when it is worth closing or
- 28:44when time it is time to stop. So
- 28:45remember, if you put and instead of or
- 28:47here, it's going to ruin everything,
- 28:48right? So we did it like this. And
- 28:51again, we need to do it one more time.
- 28:53And if I go back and run this one more
- 28:55time, hopefully it should improve our
- 28:57results.
- 28:59And there we go. So you see now instead
- 29:01of losing money, we are earning a really
- 29:04good number. So 18%
- 29:07in 6 months, max of only minus 2%, the
- 29:11win rate is 87%. So these are really
- 29:14good. The sharp ratio is 2.81. And again
- 29:17guys, remember this is while we are
- 29:20indeed paying for trading fees. So you
- 29:22see we started from this. So this is
- 29:24what we had in the previous video. And
- 29:26in this one we have this. So these
- 29:29aren't even comparable, right? By the
- 29:31way, I did cherry pick the period of the
- 29:33back test. So let's go and go a little
- 29:36bit further. So let's choose 2024. And
- 29:39here let's move this up to November,
- 29:42which we are in right now. and let's see
- 29:45what will be the results then. All
- 29:47right, so in 2025 it actually continued
- 29:49to perform really well but in 2024 it
- 29:53wasn't so great especially in this
- 29:55period. All right, so here's the thing.
- 29:56I ran the strategy in 2025 myself and it
- 30:00did perform well and as you can see in
- 30:02the back test here's the reason because
- 30:04even in the back test it was doing well
- 30:06but at the end of 2024 it wasn't doing
- 30:09well. So, I was a little bit lucky. But
- 30:12here's the thing. I made the changes
- 30:14that I did based on what made sense to
- 30:16me. I didn't even run optimization mode
- 30:19on it. I don't think we even support
- 30:21multi- trading routes in our
- 30:23optimization mode just yet. So, I didn't
- 30:25do that. I just made changes based on
- 30:27what made sense to me as I explained
- 30:29everything to you. But still, I wouldn't
- 30:31say it's a perfect strategy. As you can
- 30:33see, there are some periods that it
- 30:34could lose money. And besides that when
- 30:36I ran it live I faced one very important
- 30:39issue and that was the slippage. So in
- 30:42the trading fees we set it here to this
- 30:45number. So a little bit higher than what
- 30:47the trading fees are for market orders
- 30:49on an exchange such as Binance but in
- 30:51reality the slippage was a bit higher.
- 30:53Now I did not trade on Binance. So if
- 30:56you are you have access to more
- 30:58liquidity than what I did. So, I wasn't
- 31:01trading on Binance and the exchange that
- 31:03I was trading in, this was a default
- 31:05trading fees. So, including a slippage,
- 31:07the actual fees that I was paying was
- 31:09something like this. So, that's another
- 31:11thing that you need to remember,
- 31:12especially if you're going to trade this
- 31:14strategy on something like a memecoin. I
- 31:17would say you're going to get way more
- 31:18opportunities and better ones because
- 31:20the volatility is higher in meme coins
- 31:22and so are the insufficiencies. But that
- 31:24also means when the volatility is really
- 31:27high and remember exactly in those
- 31:29points that's where the market makers
- 31:31will stop providing liquidity and that
- 31:33will make your slippage number even
- 31:34higher. So you need to consider that. So
- 31:37increase your trading fees to a number
- 31:38such as this or even this to be prepared
- 31:41for that. And the other thing is that
- 31:43instead of just trading two pairs like I
- 31:45did here, why not trade let's say four,
- 31:48six, eight or 10 pairs at the same time,
- 31:50right? because then you're going to get
- 31:53significantly smoother equity curve
- 31:55which is really awesome. So all the
- 31:57values such as your max roon or calmer
- 32:00or sharp ratio are going to better if
- 32:02you trade more trading pairs using
- 32:04something like this. Now as you guys
- 32:06already know we do have a strategy index
- 32:08page where I submit all the strategies
- 32:10that I show you guys with the back test
- 32:12results for other symbols and time
- 32:14frames and you can check them out here.
- 32:16But I'm not going to submit this one
- 32:17because this strategy uses two different
- 32:20training pairs and our strategies page
- 32:23doesn't support that yet. But a huge
- 32:24update is coming to this page and we're
- 32:26going to have way more strategies and
- 32:28better metrics and everything on this
- 32:30page very soon. So if you haven't
- 32:31checked out this page for more
- 32:32strategies, make sure to do so. As many
- 32:34of you guys already know, I am obsessed
- 32:36with statistical arbitrage. So this
- 32:38isn't really the end. This was just the
- 32:40updates that I had for 2025. So, I will
- 32:42definitely continue working on this
- 32:44strategy and hopefully improving the
- 32:46result of it. And before I leave you,
- 32:47we're going to have a giveaway. A random
- 32:49subscriber who likes the video and
- 32:50comments their opinion is going to win 1
- 32:53million bank token. All right, let's
- 32:54pick the winner for the previous video.
- 32:56And the winner is I'm not going to read
- 32:58their name, but I do appreciate your
- 33:00comments. Now, please reach out to me so
- 33:02I can send you your bank tokens. Thank
- 33:03you so much for watching. I'll see you
- 33:05in the next video.
- 33:10>> [music]
About this transcript
This page contains the full transcript of I Rebuilt Edward Thorp's $800M Pairs-Trading Strategy for 2025 (Full Python Walkthrough) by Algo-trading with Saleh, generated from the public captions YouTube serves with the video. The transcript has 7,176 words across 973 segments, with the original timestamps preserved so you can click any line to jump to that moment in the embedded player.
What you can do with it
Use the transcript to take notes, quote the speaker, build a study guide, generate a summary with ChatGPT or Claude via the YouTube Summary tool, or export it as a timed subtitle file with YouTube to SRT. You can also re-open it in the transcriber to translate the transcript into 100+ languages.
Free YouTube transcript tool
YouTube2Text is a free YouTube transcript generator — no signup, no daily limit. Paste any YouTube link and get the full transcript instantly, with timestamps, click-to-jump, translation to 100+ languages, AI prompts for ChatGPT, Claude, and Gemini, and exports to TXT, SRT, VTT, or Markdown.