avatarSofien Kaabar, CFA

Free AI web copilot to create summaries, insights and extended knowledge, download it at here

7861

Abstract

s="hljs-number">1</span>, <span class="hljs-number">3</span>:<span class="hljs-number">4</span>] <span class="hljs-built_in">matrix</span> = <span class="hljs-built_in">np</span>.ndarray.<span class="hljs-built_in">flatten</span>(<span class="hljs-built_in">matrix</span>) <span class="hljs-built_in">matrix</span> = total * <span class="hljs-built_in">matrix</span> wma = (<span class="hljs-built_in">matrix</span>.<span class="hljs-built_in">sum</span>()) / (total.<span class="hljs-built_in">sum</span>()) weighted = <span class="hljs-built_in">np</span>.<span class="hljs-built_in">append</span>(weighted, wma)</pre></div><div id="389b"><pre>except ValueError: <span class="hljs-keyword">pass</span>

<span class="hljs-keyword">Data</span> = <span class="hljs-keyword">Data</span>[lookback - <span class="hljs-number">1</span>:, ]
weighted = np.<span class="hljs-built_in">reshape</span>(weighted, (-<span class="hljs-number">1</span>, <span class="hljs-number">1</span>)) 
<span class="hljs-keyword">Data</span> = np.concatenate((<span class="hljs-keyword">Data</span>, weighted), axis = <span class="hljs-number">1</span>)   

<span class="hljs-keyword">return</span> <span class="hljs-keyword">Data</span></pre></div><div id="a51e"><pre># <span class="hljs-keyword">For</span> this <span class="hljs-keyword">function</span> <span class="hljs-keyword">to</span> <span class="hljs-keyword">work</span>, you need <span class="hljs-keyword">to</span> have an OHLC <span class="hljs-keyword">array</span> composed <span class="hljs-keyword">of</span> the four usual <span class="hljs-keyword">columns</span>, <span class="hljs-keyword">then</span> you can use the below syntax <span class="hljs-keyword">to</span> <span class="hljs-keyword">get</span> a data <span class="hljs-keyword">array</span> <span class="hljs-keyword">with</span> the weighted moving average <span class="hljs-keyword">using</span> the lookback you need</pre></div><div id="860c"><pre><span class="hljs-attr">my_ohlc_data</span> = lwma(my_ohlc_data, <span class="hljs-number">20</span>)</pre></div><p id="4e98">Now that we have understood what a Weighted Moving Average is, we can proceed by presenting the Hull Moving Average, a powerful trend-following early system.</p><h1 id="6b5e">The Hull Moving Average</h1><p id="e5b4">The Hull Moving Average uses the Weighted Moving Average as building blocks and it is calculated following the below steps:</p><ul><li><b>Choose a lookback period such as 20 or 100 and calculate the Weighted Moving Average of the closing price.</b></li><li><b>Divide the lookback period found in the first step and calculate the Weighted Moving Average of the closing price using this new lookback period. If the number cannot by divided by two, then take the closest number before the comma (e.g. a lookback of 15 can be 7 or 8 as the second lookback).</b></li><li><b>Multiply the second Weighted Moving Average by two and subtract from it the first Weighted Moving Average.</b></li><li><b>As a final step, take the square root of the first lookback (e.g. if you have chosen a lookback of 100, then the third lookback period is 10) and calculate the Weighted Moving Average on the latest result we have had in the third step. Be careful not to calculate it on market prices.</b></li></ul><p id="6ed1">Therefore, if we choose a lookback period of 100, we will calculate on 100 lookback period, then on 50, and finally, on 10 applied to the latest result.</p><figure id="2d15"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*7BsRQkf7fXve-zhLTRhaDw.png"><figcaption>GBPUSD hourly data with the 100-period Hull Moving Average. (Image by Author)</figcaption></figure><div id="b5e0"><pre>def adder(<span class="hljs-keyword">Data</span>, times):

for i <span class="hljs-keyword">in</span> <span class="hljs-built_in">range</span>(<span class="hljs-number">1</span>, times + <span class="hljs-number">1</span>):

    z = np.zeros((len(<span class="hljs-keyword">Data</span>), <span class="hljs-number">1</span>), dtype = <span class="hljs-built_in">float</span>)
    <span class="hljs-keyword">Data</span> = np.append(<span class="hljs-keyword">Data</span>, z, axis = <span class="hljs-number">1</span>)</pre></div><div id="2818"><pre><span class="hljs-keyword">return</span> <span class="hljs-keyword">Data</span></pre></div><div id="2900"><pre>def hull_moving_average(<span class="hljs-keyword">Data</span>, what, lookback, <span class="hljs-keyword">where</span>):

<span class="hljs-keyword">Data</span> = lwma(<span class="hljs-keyword">Data</span>, lookback, what)

second_lookback = <span class="hljs-keyword">round</span>((lookback / <span class="hljs-number">2</span>), <span class="hljs-number">1</span>)
second_lookback = <span class="hljs-built_in">int</span>(second_lookback) 

<span class="hljs-keyword">Data</span> = lwma(<span class="hljs-keyword">Data</span>, second_lookback, what)

<span class="hljs-keyword">Data</span> = adder(<span class="hljs-keyword">Data</span>, <span class="hljs-number">1</span>)
<span class="hljs-keyword">Data</span>[:, <span class="hljs-keyword">where</span> + <span class="hljs-number">2</span>] = ((<span class="hljs-number">2</span> * <span class="hljs-keyword">Data</span>[:, <span class="hljs-keyword">where</span> + <span class="hljs-number">1</span>]) - <span class="hljs-keyword">Data</span>[:, <span class="hljs-keyword">where</span>])</pre></div><div id="3a72"><pre><span class="hljs-attr">third_lookback</span> = round(np.sqrt(lookback), <span class="hljs-number">1</span>)
<span class="hljs-attr">third_lookback</span> = int(third_lookback)</pre></div><div id="80e1"><pre><span class="hljs-keyword">Data</span> = lwma(<span class="hljs-keyword">Data</span>, third_lookback, <span class="hljs-keyword">where</span> + <span class="hljs-number">2</span>)</pre></div><div id="c6eb"><pre><span class="hljs-keyword">return</span> <span class="hljs-keyword">Data</span></pre></div><figure id="001b"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*0DEbuJPbIf6v76BktH0eKA.png"><figcaption>EURUSD hourly data with the 200-period Hull Moving Average. (Image by Author)</figcaption></figure><p id="2b04">Why do we say that the Hull Moving Average reduces lag? The answer comes mainly from the building blocks which are the weighted moving averages. They place more weights on more recent values. Furthermore, the lag is also reduced by offsetting one weighted moving average with the one that has half the lookback period. Finally, we take the square root of the lookback period and apply it on the moving average itself using the weighting method. This gives us a moving average close to the market price and serves as an early trend reversal indicator.</p><p id="3471">There is a special trick I like to use to add a visual component and helps to know whether the moving average is tilting to the upside or to the downside. Notice that in the chart above, when the moving average is pointing upwards, it is painted in green and when it is pointing downwards, it is painted in red. The way to do this is to simply put the following conditions:</p><ul><li>When the current moving average is greater than the one preceding it, then the next column should equal to the current moving average. Similarly, when the current moving average is less than the one preceding it, then the next column should equal to the current moving average divided by zero so that it gets an “inf” value in the array. This is so that when it is plotted on the chart, the “inf” values do not appear thus leaving the place for other values to be filled.</li><li>When the current moving average is less than the one preceding it, then the

Options

next column should equal to the current moving average. Similarly, when the current moving average is greater than the one preceding it, then the next column should equal to the current moving average divided by zero so that it gets an “inf” value in the array.</li></ul><p id="81b1">The plotting function will be used on both new columns and we will choose our colors accordingly, giving us one moving average plot stemming from two columns, here is how to do this in Python:</p><div id="c2fb"><pre>my_data[:<span class="hljs-type"></span>, <span class="hljs-keyword">new</span><span class="hljs-type">_column1</span>] = my_data[:<span class="hljs-type"></span>, hull_average] my_data[:<span class="hljs-type"></span>, <span class="hljs-keyword">new</span><span class="hljs-type">_column2</span>] = my_data[:<span class="hljs-type"></span>, hull_average]</pre></div><div id="92d6"><pre><span class="hljs-keyword">for</span> <span class="hljs-selector-tag">i</span> <span class="hljs-keyword">in</span> <span class="hljs-built_in">range</span>(<span class="hljs-built_in">len</span>(my_data)): <span class="hljs-keyword">if</span> my_data<span class="hljs-selector-attr">[i, hull_average]</span> > my_data<span class="hljs-selector-attr">[i - 1, hull_average]</span>: my_data<span class="hljs-selector-attr">[i, new_column1]</span> = my_data<span class="hljs-selector-attr">[i, hull_average]</span> my_data<span class="hljs-selector-attr">[i, new_column2]</span> = my_data<span class="hljs-selector-attr">[i, hull_average]</span> / <span class="hljs-number">0</span>

<span class="hljs-keyword">if</span> my_data<span class="hljs-selector-attr">[i, hull_average]</span> &lt; my_data<span class="hljs-selector-attr">[i - 1, hull_average]</span>:
    my_data<span class="hljs-selector-attr">[i, new_column2]</span> = my_data<span class="hljs-selector-attr">[i, hull_average]</span>  
    my_data<span class="hljs-selector-attr">[i, new_column1]</span> = my_data<span class="hljs-selector-attr">[i, hull_average]</span> / <span class="hljs-number">0</span> 
    

plt<span class="hljs-selector-class">.plot</span>(my_data<span class="hljs-selector-attr">[-1000:, new_column1]</span>, <span class="hljs-attribute">color</span> = <span class="hljs-string">'green'</span>) plt<span class="hljs-selector-class">.plot</span>(my_data<span class="hljs-selector-attr">[-1000:, new_column2]</span>, <span class="hljs-attribute">color</span> = <span class="hljs-string">'red'</span>)</pre></div><p id="56c8">You can use this trick for pretty much any type of data.</p><figure id="d6e2"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*SeqiGKk_VyDWs1rbnOMxvA.png"><figcaption>EURUSD hourly data with the 50-period Hull Moving Average. (Image by Author)</figcaption></figure><h1 id="a992">Using the Hull Moving Average</h1><p id="35e3">The Hull Moving Average is used basically as you use any other moving average. However, I have a preference to use its slope as a signal rather than use it as a means to find support and resistance levels. Therefore, the difference (in my personal opinion) between the Hull Moving Average and the simple one is that the former is better in detecting early trend reversals but the latter is better used as support and resistance levels.</p><div id="ab5e"><pre>def signal(Data, what, buy, sell):

for i in range(len(Data)):
        
    if Data<span class="hljs-comment">[i, what]</span> &gt; Data<span class="hljs-comment">[i - 1, what]</span> and Data<span class="hljs-comment">[i - 1, what]</span> &lt; Data<span class="hljs-comment">[i - 2, what]</span>:
        Data<span class="hljs-comment">[i, buy]</span> = 1
        
    if Data<span class="hljs-comment">[i, what]</span> &lt; Data<span class="hljs-comment">[i - 1, what]</span> and Data<span class="hljs-comment">[i - 1, what]</span> &gt; Data<span class="hljs-comment">[i - 2, what]</span>:
        Data<span class="hljs-comment">[i, sell]</span> = -1</pre></div><div id="365d"><pre><span class="hljs-comment"># The what variable refers to the Hull Moving Average, and the buy and sell variables are the columns you want to put the long/short orders in</span></pre></div><figure id="e1bd"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*0zUsFiP0bXpNfxs8kuuLuQ.png"><figcaption>Signal chart on the EURUSD hourly data following the Hull Moving Average Strategy. (Image by Author)</figcaption></figure><p id="1681">I am not a big fan of approaching zero lag as many indicators try to do. If you want to achieve zero lag, then all you need to do is to look at the <i>price chart with no indicator overlaid on it</i>. Some technical analysts claim to have zero lag moving averages which is very absurd because that means that the moving average would simply follow the price at every tick. How is that useful?</p><p id="5fcb">My preferred way of using moving averages is by simply confirming my support and resistance levels found through other ways.</p><p id="3618">If you want to see how to create all sorts of algorithms yourself, feel free to check out Lumiwealth. From algorithmic trading to blockchain and machine learning, they have <i>hands-on</i> detailed courses that I highly recommend.</p><div id="fead" class="link-block">
      <a href="https://www.lumiwealth.com/algorithmic-trading-landing-page/?utm_source=influence&amp;utm_medium=medium&amp;utm_campaign=sofien">
        <div>
          <div>
            <h2>Learn Algorithmic Trading with Python Lumiwealth</h2>
            <div><h3>undefined</h3></div>
            <div><p>undefined</p></div>
          </div>
          <div>
            <div style="background-image: url(https://miro.readmedium.com/v2/resize:fit:320/0*FbCz_ARtbOdywMlC)"></div>
          </div>
        </div>
      </a>
    </div><h1 id="e420">Summary</h1><p id="05a4">To sum up, what I am trying to do is to simply contribute to the world of objective technical analysis which is promoting more transparent techniques and strategies that need to be back-tested before being implemented. This way, technical analysis will get rid of the bad reputation of being subjective and scientifically unfounded.</p><p id="570c">Medium is a hub to interesting reads. I read a lot of articles before I decided to start writing. Consider joining Medium using my referral link (at <b>NO </b>additional cost to you).</p><div id="de8c" class="link-block">
      <a href="https://kaabar-sofien.medium.com/membership">
        <div>
          <div>
            <h2>Join Medium with my referral link — Sofien Kaabar</h2>
            <div><h3>As a Medium member, a portion of your membership fee goes to writers you read, and you get full access to every story…</h3></div>
            <div><p>kaabar-sofien.medium.com</p></div>
          </div>
          <div>
            <div style="background-image: url(https://miro.readmedium.com/v2/resize:fit:320/0*XBqK-oHjNF_4ydU8)"></div>
          </div>
        </div>
      </a>
    </div><p id="5d41">I recommend you always follow the the below steps whenever you come across a trading technique or strategy:</p><ul><li><i>Have a critical mindset and get rid of any emotions.</i></li><li><i>Back-test it using real life simulation and conditions.</i></li><li><i>If you find potential, try optimizing it and running a forward test.</i></li><li><i>Always include transaction costs and any slippage simulation in your tests.</i></li><li><i>Always include risk management and position sizing in your tests.</i></li></ul><p id="beb3">Finally, even after making sure of the above, stay careful and monitor the strategy because market dynamics may shift and make the strategy unprofitable.</p></article></body>

The Hull Moving Average as a Trend Reversal Indicator

Presenting and Coding the Hull Moving Average in Time Series Analysis

The Hull Moving Average is a powerful trend-following overlay indicator that can be used to determine trends and capture them with less lag than the simple moving average. Its calculation is more complex but it is worth adding to the trading framework as it a confirmation factor. In this article, we will discuss the building blocks of the Hull Moving Average before coding and visualizing it.

Knowledge must be accessible to everyone. This is why, from now on, a purchase of either one of my new books Contrarian Trading Strategies in PythonorTrend Following Strategies in Pythoncomes with free PDF copies of my first three books (Therefore, purchasing one of the new books gets you 4 books in total). The two new books listed above feature a lot of advanced indicators and strategies with a GitHub page. You can use the below link to purchase one of the two books (Please specify which one and make sure to include your e-mail in the note).

The Concept of Moving Averages

Moving averages help us confirm and ride the trend. They are the most known technical indicator and this is because of their simplicity and their proven track record of adding value to the analyses. We can use them to find support and resistance levels, stops and targets, and to understand the underlying trend. This versatility makes them an indispensable tool in our trading arsenal.

EURUSD with its 200-period Simple moving average. (Image by Author)

As the name suggests, this is your plain simple mean that is used everywhere in statistics and basically any other part in our lives. It is simply the total values of the observations divided by the number of observations. Mathematically speaking, it can be written down as:

We can see that the moving average is providing decent dynamic support and resistance levels from where we can place our orders in case the market goes down there.

def ma(Data, lookback, what, where):
    
  for i in range(len(Data)):
      try:
        Data[i, where] = (Data[i - lookback + 1:i + 1, what].mean())
        
            except IndexError:
                pass
    return Data

Another even more dynamic moving average is the exponential one. Its idea is to give more weight to the more recent values so that it reduces the lag between the price and the average.

EURUSD with its 200-period Exponential moving average. (Image by Author)

Notice how the exponential moving average is closer to prices than the simple one when the trend is strong. This is because it gives more weight to the latest values so that the average does not stay very far.

Let us now look at the first building block for the Hull Moving Average, i.e. the Weighted Moving Average.

Check out my weekly market sentiment report to understand the current positioning and to estimate the future direction of several major markets through complex and simple models working side by side. Find out more about the report through this link that covers the analysis between 07/08/2022 and 14/08/2022:

The Weighted Moving Average

Also referred to as a linear-weighted moving average, the weighted moving average is a simple moving average that places more weight on recent data. The most recent observation has the biggest weight and each one prior to it has a progressively decreasing weight. Intuitively, it has less lag than the other moving averages but it is also the least used, and hence, what it gains in lag reduction, it loses in popularity.

Mathematically speaking, it can be written down as:

EURUSD hourly data with a 200-period Weighted Moving Average. (Image by Author)

Basically, if we have a dataset composed of two numbers [1, 2] and we want to calculate a linear weighted average, then we will do the following:

  • (2 x 2) + (1 x 1) = 5
  • 5 / 3 = 1.66

This assumes a time series with the number 2 as being the most recent observation.

USDCHF hourly data with a 200-period Weighted Moving Average. (Image by Author)
import numpy as np
def lwma(Data, lookback):
    
    weighted = []
    for i in range(len(Data)):
            try:
                total = np.arange(1, lookback + 1, 1)
                
                matrix = Data[i - lookback + 1: i + 1, 3:4]
                matrix = np.ndarray.flatten(matrix)
                matrix = total * matrix
                wma = (matrix.sum()) / (total.sum())
                weighted = np.append(weighted, wma)
except ValueError:
                pass
    
    Data = Data[lookback - 1:, ]
    weighted = np.reshape(weighted, (-1, 1)) 
    Data = np.concatenate((Data, weighted), axis = 1)   
    
    return Data
# For this function to work, you need to have an OHLC array composed of the four usual columns, then you can use the below syntax to get a data array with the weighted moving average using the lookback you need
my_ohlc_data = lwma(my_ohlc_data, 20)

Now that we have understood what a Weighted Moving Average is, we can proceed by presenting the Hull Moving Average, a powerful trend-following early system.

The Hull Moving Average

The Hull Moving Average uses the Weighted Moving Average as building blocks and it is calculated following the below steps:

  • Choose a lookback period such as 20 or 100 and calculate the Weighted Moving Average of the closing price.
  • Divide the lookback period found in the first step and calculate the Weighted Moving Average of the closing price using this new lookback period. If the number cannot by divided by two, then take the closest number before the comma (e.g. a lookback of 15 can be 7 or 8 as the second lookback).
  • Multiply the second Weighted Moving Average by two and subtract from it the first Weighted Moving Average.
  • As a final step, take the square root of the first lookback (e.g. if you have chosen a lookback of 100, then the third lookback period is 10) and calculate the Weighted Moving Average on the latest result we have had in the third step. Be careful not to calculate it on market prices.

Therefore, if we choose a lookback period of 100, we will calculate on 100 lookback period, then on 50, and finally, on 10 applied to the latest result.

GBPUSD hourly data with the 100-period Hull Moving Average. (Image by Author)
def adder(Data, times):
    
    for i in range(1, times + 1):
    
        z = np.zeros((len(Data), 1), dtype = float)
        Data = np.append(Data, z, axis = 1)
return Data
def hull_moving_average(Data, what, lookback, where):
    
    Data = lwma(Data, lookback, what)
    
    second_lookback = round((lookback / 2), 1)
    second_lookback = int(second_lookback) 
    
    Data = lwma(Data, second_lookback, what)
    
    Data = adder(Data, 1)
    Data[:, where + 2] = ((2 * Data[:, where + 1]) - Data[:, where])
third_lookback = round(np.sqrt(lookback), 1)
    third_lookback = int(third_lookback)
Data = lwma(Data, third_lookback, where + 2)
return Data
EURUSD hourly data with the 200-period Hull Moving Average. (Image by Author)

Why do we say that the Hull Moving Average reduces lag? The answer comes mainly from the building blocks which are the weighted moving averages. They place more weights on more recent values. Furthermore, the lag is also reduced by offsetting one weighted moving average with the one that has half the lookback period. Finally, we take the square root of the lookback period and apply it on the moving average itself using the weighting method. This gives us a moving average close to the market price and serves as an early trend reversal indicator.

There is a special trick I like to use to add a visual component and helps to know whether the moving average is tilting to the upside or to the downside. Notice that in the chart above, when the moving average is pointing upwards, it is painted in green and when it is pointing downwards, it is painted in red. The way to do this is to simply put the following conditions:

  • When the current moving average is greater than the one preceding it, then the next column should equal to the current moving average. Similarly, when the current moving average is less than the one preceding it, then the next column should equal to the current moving average divided by zero so that it gets an “inf” value in the array. This is so that when it is plotted on the chart, the “inf” values do not appear thus leaving the place for other values to be filled.
  • When the current moving average is less than the one preceding it, then the next column should equal to the current moving average. Similarly, when the current moving average is greater than the one preceding it, then the next column should equal to the current moving average divided by zero so that it gets an “inf” value in the array.

The plotting function will be used on both new columns and we will choose our colors accordingly, giving us one moving average plot stemming from two columns, here is how to do this in Python:

my_data[:, new_column1] = my_data[:, hull_average]
my_data[:, new_column2] = my_data[:, hull_average]
for i in range(len(my_data)):
    if my_data[i, hull_average] > my_data[i - 1, hull_average]:
        my_data[i, new_column1] = my_data[i, hull_average]
        my_data[i, new_column2] = my_data[i, hull_average] / 0
        
    if my_data[i, hull_average] < my_data[i - 1, hull_average]:
        my_data[i, new_column2] = my_data[i, hull_average]  
        my_data[i, new_column1] = my_data[i, hull_average] / 0 
        
plt.plot(my_data[-1000:, new_column1], color = 'green')
plt.plot(my_data[-1000:, new_column2], color = 'red')

You can use this trick for pretty much any type of data.

EURUSD hourly data with the 50-period Hull Moving Average. (Image by Author)

Using the Hull Moving Average

The Hull Moving Average is used basically as you use any other moving average. However, I have a preference to use its slope as a signal rather than use it as a means to find support and resistance levels. Therefore, the difference (in my personal opinion) between the Hull Moving Average and the simple one is that the former is better in detecting early trend reversals but the latter is better used as support and resistance levels.

def signal(Data, what, buy, sell):
    
    for i in range(len(Data)):
            
        if Data[i, what] > Data[i - 1, what] and Data[i - 1, what] < Data[i - 2, what]:
            Data[i, buy] = 1
            
        if Data[i, what] < Data[i - 1, what] and Data[i - 1, what] > Data[i - 2, what]:
            Data[i, sell] = -1
# The what variable refers to the Hull Moving Average, and the buy and sell variables are the columns you want to put the long/short orders in
Signal chart on the EURUSD hourly data following the Hull Moving Average Strategy. (Image by Author)

I am not a big fan of approaching zero lag as many indicators try to do. If you want to achieve zero lag, then all you need to do is to look at the price chart with no indicator overlaid on it. Some technical analysts claim to have zero lag moving averages which is very absurd because that means that the moving average would simply follow the price at every tick. How is that useful?

My preferred way of using moving averages is by simply confirming my support and resistance levels found through other ways.

If you want to see how to create all sorts of algorithms yourself, feel free to check out Lumiwealth. From algorithmic trading to blockchain and machine learning, they have hands-on detailed courses that I highly recommend.

Summary

To sum up, what I am trying to do is to simply contribute to the world of objective technical analysis which is promoting more transparent techniques and strategies that need to be back-tested before being implemented. This way, technical analysis will get rid of the bad reputation of being subjective and scientifically unfounded.

Medium is a hub to interesting reads. I read a lot of articles before I decided to start writing. Consider joining Medium using my referral link (at NO additional cost to you).

I recommend you always follow the the below steps whenever you come across a trading technique or strategy:

  • Have a critical mindset and get rid of any emotions.
  • Back-test it using real life simulation and conditions.
  • If you find potential, try optimizing it and running a forward test.
  • Always include transaction costs and any slippage simulation in your tests.
  • Always include risk management and position sizing in your tests.

Finally, even after making sure of the above, stay careful and monitor the strategy because market dynamics may shift and make the strategy unprofitable.

Trading
Investing
Finance
Cryptocurrency
Data Science
Recommended from ReadMedium