Building a Mean Reversion Strategy with the lemon.markets API & hosting it in the cloud


7. September 2021
Trading


Hi there. My name is Marius and I am our community developer at lemon.markets 🍋. We are building an API that allows you to build your own brokerage experience at the stock market. This may include, among other possibilities, your very own automated trading strategy. To inspire you a little bit for your first project with us: Might I suggest a starting point?

In this post, I want to dive into how you can use the lemon.markets API to build one of the most well-known trading strategies: the mean reversion strategy. And, to set you up for success, I will also walk you through the steps to host your strategy up in the cloud using Heroku, to make sure your program continues to run, even when your laptop is closed.

Title Card for "Building a Mean Reversion Strategy with the lemon.markets API"

The premise of the mean-reversion strategy (I am going to call it MR strategy from now on because who likes words that fill up entire lines?) is easily explained: behind it stands the assumption that a stock will eventually converge towards a mean (or “average”) value. So imagine you have a stock from the up-and-coming online supermarket “SuperCompany” that has a mean share price of €97.00 (e.g. over the past two weeks) and is currently traded at €92.50. The MR strategy assumes that the price will soon converge towards the average share price, meaning that now would be a good time to buy (#buythedip, anyone?). On the other hand, if the current share price was €103.50 right now, it would be (if we follow the MR logic) a good idea to sell the stock, as it is currently higher than the average share price over the past few weeks and we therefore expect it to decrease soon.

Sounds simple, right? It is.

And more importantly, the logic behind it is easy to implement if we have the right infrastructure in place. And, I’m sure you guessed it: that’s where lemon.markets comes into play. Using the lemon.markets API, you can build a MR strategy on your own with very little effort. Word has it that Jim Simons’ early successes can be attributed to a MR-based strategy. For anyone interested in diving deeper inside the mean reversion strategy, we can recommend this article.

But for now, let’s dive right in.

GIF of Stephen Colbert

Implementing the Mean Reversion Strategy

You can find the whole implementation, ready to host in the cloud, in our GitHub repository. However, for this article, let us take a look at some of the main characteristics of the mean reversion code.

One crucial ingredient to the project is the lemon.markets Python SDK. The SDK makes communicating with the API a lot easier and saves us a ton of time, as we do not need to specify endpoint function calls on our own and can simply use the ones defined in the SDK. You can easily install it via:

1pip install lemon

Afterwards, you have access to all functions and can place orders or retrieve market data. We will see in a second how that comes in handy for our mean reversion project.

Environment Variables

As we do not want to expose sensitive data (e.g. our API Keys) in our main script, we use a number of environment variables. If you want to run your script locally, we suggest that you create a .envfile containing the following variables, which can then be accessed via

1os.environ.get(“ENV-VARIABLE")
1| ENV Variable   |      Explanation      |  
2|----------|:-------------:|
3| DATA_API_KEY |  Your Market Data API Key | 
4| TRADING_API_KEY |  Your Trading API Key | 
5|MIC| Market Identifier Code of Trading Venue|

Environment Variables for Mean Reversion Python script

After we took care of the project set up, we can start writing the general mean reversion logic in our main script.

At first, we use the SDK to create an API client, like so:

1client = api.create(    
2trading_api_token=os.environ.get('PAPER_TRADING_API_KEY'),
3market_data_api_token=os.environ.get('DATA_API_KEY'),    
4env='paper'
5)

As you can see, we use our previously established environment variables to instantiate the client. Now we can use the client to make all subsequent API requests and define the mean reversion logic.

Defining our main script

Moving Average

In order to be able to make a decision based on a mean stock price, we obviously need to make sure that we actually have a calculated mean price that we can use. In our script, we actually implemented two different ways to calculate the mean value:

  1. the simple moving average (which is the “standard” mean value, where you divide the sum of results by n)
  2. the exponential moving average (where newer prices are weighted higher)

Later in the script, you can choose which one you want to use for your mean reversion decision. To calculate the mean data we use the lemon.markets Market Data API, specifically the OHLC endpoint, which allows us to retrieve historical Open-High-Low-Close Data for specific instruments:

1def simple_moving_average_calculator(isin, from_date, num_days=10):
2    """
3    :param isin: isin of stock/ETF you want to calculate the average for
4    :param num_days: number of days you wish to include in the average
5    :param from_date: the date you want to calculate the SMA for
6    :return: SMA as a number
7    """
8    prices_close = []
9    while len(prices_close) < num_days:
10        market_data = client.market_data.ohlc.get(
11            isin=isin,
12            from_=from_date.strftime('%Y-%m-%d'),
13            to=from_date.strftime('%Y-%m-%d'),
14            decimals=True,
15            period='d1',
16            mic=os.getenv("MIC")
17        )
18        if len(market_data.results) != 0:  # make sure that we aren't counting weekends/holidays
19            prices_close.append(market_data.results[0].c)
20        from_date = (from_date - timedelta(days=1))
21    return statistics.mean(prices_close)  # SMA calculation
22def exponential_moving_average_calculator(isin, from_date, num_days=10, smoothing=2):
23    """
24    :param isin: isin of stock/ETF you want to calculate the average for
25    :param num_days: number of days you wish to include in the average
26    :param from_date: the date you want to calculate the EMA for
27    :param smoothing: the smoothing factor you want to use for your EMA calculations
28    :return: EMA as a number
29    """
30    past_x_days = []
31    while len(past_x_days) < num_days + 1:  # make a list of the last x days the market was open
32        market_data = client.market_data.ohlc.get(
33            isin=isin,
34            from_=from_date.strftime('%Y-%m-%d'),
35            to=from_date.strftime('%Y-%m-%d'),
36            period='d1'
37        )
38        if len(market_data.results) != 0:  # make sure that we aren't counting weekends/holidays
39            past_x_days.insert(0, from_date)
40        from_date = (from_date - timedelta(days=1))
41    exponential_moving_avg = 0
42    ema_yest = simple_moving_average_calculator(isin=isin, from_date=past_x_days[0])
43    multiplier = smoothing / (num_days + 1)
44    for day in past_x_days:  # initialize all variables above, then recursively find the EMA
45        day_x_close_price = client.market_data.ohlc.get(
46            period='d1',
47            isin=isin,
48            from_=day.strftime('%Y-%m-%d'),
49            to=day.strftime('%Y-%m-%d'),
50            decimals=True,
51            mic=os.getenv("MIC")).results[0].c
52        exponential_moving_avg = day_x_close_price * multiplier + ema_yest * (1 - multiplier)
53        ema_yest = exponential_moving_avg
54    return exponential_moving_avg

Mean Reversion Logic

After we’ve calculated the moving average value(s), we can use it to define our mean reversion logic:

1def mean_reversion_decision(isin: str):
2    """
3    :param isin: pass the isin of your instrument
4    :return: returns whether you should buy (True) or sell (False), depending on MR criteria
5    """
6    simple_moving_avg = simple_moving_average_calculator(isin=isin, from_date=datetime.now())
7    print(f'Simple Moving Average Price: {simple_moving_avg}')
8    exponential_moving_avg = exponential_moving_average_calculator(isin=isin, from_date=datetime.now())
9    print(f'Exponential Moving Average Price: {exponential_moving_avg}')
10    latest_close_price = client.market_data.ohlc.get(
11        period='d1',
12        isin=isin,
13        from_=datetime.now().strftime('%Y-%m-%d'),
14        decimals=True,
15        mic=os.getenv("MIC")).results[0].c
16    if latest_close_price < exponential_moving_avg:  # change this line to use SMA or EMA
17        return True
18    return False

Using the /ohlc endpoint, we can select a specific instrument via its ISIN to calculate both the moving average and the latest close price. Following the idea behind the mean reversion strategy, in line 18 we define a simple IF/THEN-clause to decide whether we want to buy (“True”) or sell (“False”). We can subsequently use that logic to perform the mean reversion logic “in action”.

We therefore define our main function mean_reversion(). Here, we pass a specific instrument (Volkswagen in this case, but you can obviously change that to the one you are interested in), check if our Trading Venue is currently open and execute our mean reversion logic. We also use our previously defined mean_reversion_decision function to come to a decision for the specific instrument. Based on that, we either buy (if we have enough cash to do so), or sell (if we own enough shares to do so).

1def mean_reversion(isin: str = "DE0007664039"):
2    """
3    :param isin: pass the isin of the stock you are interested in,
4                the default is Volkswagen here, but you can obviously use any ISIN you like :)
5    """
6    load_dotenv()
7    venue = client.market_data.venues.get(os.getenv("MIC")).results[0]
8    if not venue.is_open:  # make sure the venue is actually open
9        print(f"Your selected venue, {venue.name}, is not open today. Next opening day is: "
10              f"{venue.opening_days[0].day}-{venue.opening_days[0].month}-{venue.opening_days[0].year}")
11        return
12    quantity = 2
13    price = client.market_data.quotes.get_latest(isin=isin).results[0].a
14    if price * quantity < 50:  # make sure the order amount is large enough to pass through the api
15        print(f"This order totals, €{price * quantity}, which is below the minimum order amount of €50.")
16    # check for MR decision
17    if mean_reversion_decision(
18            isin=isin
19    ):
20        # create a buy order if True is returned by MR decision function
21        try:
22            print('buy')
23            placed_order = client.trading.orders.create(
24                isin=isin,
25                expires_at=7,
26                side="buy",
27                quantity=quantity,
28                venue=os.getenv("MIC"),
29            )
30            order_id = placed_order.results.id
31            # subsequently activate the order
32            activated_order = client.trading.orders.activate(order_id)
33            print(activated_order)
34        except Exception as e:
35            print(f'1{e}')
36    else:
37        try:
38            # create a sell order if mean reversion decision returns False
39            print('sell')
40            placed_order = client.trading.orders.create(
41                isin=isin,
42                expires_at=7,
43                side="sell",
44                quantity=quantity,
45                venue=os.getenv("MIC"),
46            )
47            # if position in portfolio, activate order
48            if placed_order is not None:
49                order_id = placed_order.results.id
50                activated_order = client.trading.orders.activate(order_id)
51                print(activated_order)
52            else:
53                print("You do not have sufficient holdings to place this order.")
54        except Exception as e:
55            print(f'2{e}')

Scheduling

The big perk of an automated strategy is that it is (well) automated, meaning you define the logic once and then do not need be manually involved in placing trades anymore. However, to make sure that our mean reversion logic is executed regularly, we need to use scheduling to perform the script each x hours. In our case, we use theAdvance Python scheduler(apscheduler) to execute our script hourly on each day of the week the stock exchange is open. The package makes it super easy to do that, using a few lines of code at the end of our script:

1if __name__ == '__main__':
2    scheduler = BlockingScheduler(timezone=utc)  # coordinated universal time, CET is UTC+1 (CEST is UTC+2)
3    for x in range(13):
4        scheduler.add_job(mean_reversion,
5                          trigger=CronTrigger(day_of_week="mon-fri",
6                                              hour=6 + x,
7                                              minute=30,
8                                              timezone=utc),
9                          name="Perform Mean Reversion Hourly")
10    print('Press Ctrl+{0} to exit'.format('Break' if os.name == 'nt' else 'C'))
11    try:
12        scheduler.start()
13    except (KeyboardInterrupt, SystemExit):
14        pass

And that was basically it. You can find the whole repo onGitHub— feel free to test it out. Obviously, we’d be super grateful if you decide to contribute by opening a PR or sending us a message tosupport@lemon.markets. Looking forward to your comments/improvements 🙂.

Hosting in the Cloud

Obviously, an important part of a trading algorithm is to make sure that is constantly running. There are a number of possibilities to host your Python (or any other) script in the cloud. We decided to go with Heroku, as it is extremely convenient and fast to set up.

To host a Python script in the cloud, go to your dashboard and create a new application. After this step is done, go to the application’s “Deploy” tab and connect to your GitHub repository, where the script is hosted. Choose automatic deploys if you want to trigger a new deploy every time you make changes to the repository, or manual deploy if you wish to have a bit more “manual control” over your Heroku deploys.

For those of you who read this blog post carefully, you will have noticed that we still need to configure our environment variables. You can do so by either entering them in the dashboard under Settings/Config Vars or by logging into the Heroku CLI and setting them using:

1heroku config:set ENV_VAR=value

Afterwards, you can deploy your application. It might take a few minutes until your app is up and running. You can check the status by typing

1heroku logs

in your terminal/console). If the app is not running, try typing:

1heroku ps:scale worker=1

Take a look at thisthis or this article if you should get stuck at some point during the Heroku setup.

If everything is up and running, you can relax, sit back, order some pizza and let the algorithm do the work for you 😎. Because at the end, this is what lemon.markets is there for: automating your trades so you have more time for other things.

Additional Considerations

There are a few additional decisions you need to make when building your own MR strategy. For example, how to determine the mean? Will you look at a time frame of two weeks, like we did? Or, will you look at hourly data? In addition, you do not need to restrict MR to prices, you can also look, for example, at a stock’s price-to-earnings (P/E) ratio. In our code snippet, we make trades if they are absolutely bigger or smaller than the mean, but perhaps you have reason to build your strategy such that orders are only placed if the current price is >0.05% larger than the mean.

As you can see, there is more than one way to make this base strategy more complex (and hopefully sophisticated).

Mean Reversion or Momentum?

You might have noticed that the MR strategy is built upon a critical assumption, namely that any extreme price movement will be followed by a return to long-run averages. But, is this a reasonable assumption to make? After all, a dramatic drop in instrument price might be persistent, for example, if it turns out that a stock loses its relevance in the market.

The MR strategy is based on regression to the mean, a statistical concept that describes the phenomenon that an unlikely event is usually followed by an expected event, rather than a more unlikely one. What’s the catch? This concept works only for systems with a normal distribution, which the stock market isn’t. Instead, stock returns more closely resemble a distribution with fat tails. In other words, in the stock market, extreme events happen more frequently than expected.

Why is MR so widely used, then? Many traders believe that the stock market oscillates between periods of mean reversion and momentum. In simple English, when an instrument price is increasing, it continues to increase (and the same can be said for downwards movements). (By the way: periods of momentum are why we see fat tails!) Mean reversion might not reflect the marketalways, but it does sometimes. So, how can you know whether we’re in a period of mean reversion or momentum? That’s the big question 😉 There are a few measures that can be applied to historical price data, such as the Hurst exponent. You can use this scalar to characterise a time series as mean-reverting, random-walk or trending. Based on that, you might want to decide whether to employ a mean reversion or trend following strategy. If you are interested in this sort of stuff, you can continue readinghere.

I hope you got an idea of what you can do with the lemon.markets API. Let us know what you think, tell us about a bug you found or share a strategy that you built. Simply hit us up at support@lemon.markets or join our Slack community. We’d love to hear from you 🍋 💛 .

Marius

Das könnte Dich auch interessieren

blog photo

Using Time Series Forecasting to predict stock prices 🔮

In this article you will learn what time series forecasting is and how its application in finance looks like. Then you'll also dive into Facebook's Prophet Model for Time Series Forecasting and use it together with the lemon.markets Market Data API to forecast the development of the Tesla Stock.

blog photo

Dummies Guide to Trading with Machine Learning

Ever wonder how a trader with decades of experience on thousands of stocks and lightning fast reaction times might perform in the market? With some machine learning knowledge, you might be able to automate such a trader yourself! 💻 📈

blog photo

4 reasons why YOU should automate your trading strategy

In the current volatile market conditions, everyone is trying to find ways to minimise portfolio loss. In that context, have you ever thought about automating your trading strategy? In this article, we will dive into 4 reasons for doing so. Expect to learn how it can save you time, make your trading more efficient and lead to data-based decisions.

Tiefer eintauchen

Finde weitere Ressourcen für einen einfachen Einstieg

In unserer Dokumentation erfahrt Ihr mehr über unsere API-Struktur, die verschiedenen Endpunkte und spezifische Anwendungsfälle.

Austauschen

Tritt der lemon.markets Community bei

Tritt unserem Slack-Channel bei, um Dich aktiv an unserer Community zu beteiligen, Fragen an andere Nutzer:innen zu stellen und immer auf dem Laufenden zu bleiben.

Team unterstützen

Lust lemon.markets mit uns zu bauen?

Wir sind immer auf der Suche nach großartigen Ergänzungen für unser Team, die uns beim Aufbau einer Brokerage Infrastruktur für das 21. Jahrhundert helfen.

Brauchst Du Hilfe?
Stell Deine Fragen in unserer CommunityStell Deine Fragen in unserer CommunityLeg mit unserer API Dokumentation losLeg mit unserer API Dokumentation losLass Dich von unserem Blog inspirierenLass Dich von unserem Blog inspirieren
© lemon.markets 2022DatenschutzImpressum
Systems are down

Als vertraglich gebundener Vermittler gemäß § 3 Abs. 2 WpIG für Rechnung und unter der Haftung der DonauCapital Wertpapier GmbH, Passauer Str. 5, 94161 Ruderting (kurz: DonauCapital), bietet Dir die lemon.markets GmbH, die Anlagevermittlung (§ 2 Abs. 2 Nr. 3 WpIG) von Finanzinstrumenten im Sinne des § 3 Abs. 5 WpIG sowie die Vermittlung von Konten an.