Using the API to DIY your ETF savings-plan using cost averaging

blog photo
Joanne SnelOctober 20, 2021

Or, in other words, setting up a ‘lazy’ automation for your periodically scheduled trades. 

Hey! I’m Joanne and I’m part of 🍋 , a start-up that’s making the brokerage experience accessible to developers. We’re creating an API that allows you to participate in and interact with the stock market, for example by implementing an automated trading strategy or creating a dashboard that visualises your portfolio.

In this article, I’ll walk you through a simple dollar-cost averaging strategy with which you can automate your savings plan. Nothing fancy — just making your life easier 😉. And, to top it off, I’ll also show you how to set up a Telegram bot that notifies you whenever a trade is placed. Even if you’re unsure of what to build with the API, this article is bound to get your creative juices flowing. You can begin by signing up to, here

What is ‘dollar-cost averaging’?

If you’re already investing by placing, for example, 10% of your monthly salary into an ETF or mutual fund, you’re dollar-cost averaging (maybe we should call it euro-cost averaging). Many ETF savings plans follow a similar set-up. So, what does this strategy actually entail? Imagine you have a large sum of money, say €10.000, that you want to invest. If you’re dollar-cost averaging (DCA from now on), you’d choose to spread out your buy-ins, perhaps by investing €1.000 monthly for a period of 10 months. The opposite of DCA is lump-sum investing, which would entail investing the entirety of the sum at one point in time. 

Why choose one over the other? Many investors choose DCA over lump-sum investing because it mitigates risk. You can expect the price of your chosen instrument to increase and decrease over these 10 months, but by consistently investing, you are ‘averaging out’ your cost basis (i.e. the average purchase price). Ideally, you’d just want to buy during downturns, but word has it, you can’t predict the market. 🤷‍♀ So, why would you ever do anything but DCA? The benefit of lump-sum investing is that your money begins to work for you right away. On the other hand, you run the danger of poorly timing the market, resulting in a too high buy-in price.

And why not use a platform that offers a pre-made savings plan? Simple: customisation and transparency. With a pre-made savings plan, you lack the flexibility to indicate trade execution date and time — and, very often, these happen at unfavourable times, which can cost you. Designing your savings strategy with the API means you have full control. You can avoid common buy-in dates, such as the beginning and end of the month. This way, you’ll have a price advantage over the others DCA-ing. You can choose what to trade, when your trades are executed, on which exchange to trade, how often to trade and for how long to trade. Also, you’re not limited to just ETFs — maybe you want to DCA Pinterest shares. It’s up to you.

Automating the DCA strategy 💵

Sure, you could sign into your brokerage account and manually place a trade weekly. But, you can also automate this process. I don’t know about you, but the latter sounds like much more fun (and future-you will probably thank you). Let’s jump right into the coding bit.

I’ve decided to use Python for this application, but feel free to use your preferred language — the logic remains the same. 

Setting the Scene

Throughout this script, we will need to make GET, PUT and POST requests to the API. For that reason, we create a file with the class RequestHandler, which will, you guessed it, handle all of our requests. We create two separate GET functions because we need to make GET requests to both our Trading and Market Data APIs. 

1import os
2import requests
3from dotenv import load_dotenv
4class RequestHandler:
5    def __init__(self):
6        load_dotenv()
7        self.headers = {'Authorization': 'Bearer ' + os.environ.get('TOKEN_KEY')}
8        self.url_trading: str = os.environ.get("TRADING_URL")
9        self.url_market: str = os.environ.get("MARKET_URL")
10        self.auth_url: str = os.environ.get("AUTH_URL")
11    def get_token(self, endpoint: str, data):
12        response = + endpoint, data)
13        return response
14    def get_data_trading(self, endpoint: str):
15        response = requests.get(self.url_trading + endpoint, headers=self.headers)
16        return response.json()
17    def get_data_market(self, endpoint: str):
18        response = requests.get(self.url_market + endpoint, headers=self.headers)
19        return response.json()
21    def put_data(self, endpoint: str):
22        response = requests.put(self.url_trading + endpoint, headers=self.headers)
23        return response.json()
24    def post_data(self, endpoint: str, data):
25        response = + endpoint, data, headers=self.headers)
26        return response.json()

Environment Variables

Notice how we use os.environ.get() to access several environment variables? We need to define these variables either in a separate .env file, locally within your IDE or on whichever cloud application platform you’re using to host your script, such as Heroku.

These are the environment variables you’ll need to provide if you want to run this script:

1Environment Variable, Description
2TOKEN_KEY, Your access token
3CLIENT_ID, Your client ID
4CLIENT_SECRET, Your client secret
5MIC, Market identifier code of Trading Venue
6TRADING_URL, URL of our trading API
7MARKET URL, URL of our market-data API
8AUTH_URL, URL of our authentication API


In the GitHub repository for this project, you’ll notice that we’ve defined a directory called ‘models’ which includes three files:, and These are all objects with specific properties and behaviours — they also directly reflect the structure of our API, you can read more about this in our documentation.

Order Class

An order is characterised by its ISIN, among other features — again, see our documentation for the full run-down. An order needs to be placed, activated and retrieved. These functionalities can be seen in the Order class as defined below.

1import os
2from dotenv import load_dotenv
3from helpers import RequestHandler
4class Order(RequestHandler):
5    def place_order(self, isin: str, valid_until: float, quantity: int, side: str):
6        order_details = {
7            "isin": isin,
8            "valid_until": valid_until,
9            "side": side,
10            "quantity": quantity,
11        }
12        load_dotenv()
13        space_uuid = os.getenv("SPACE_UUID")
14        endpoint = f'spaces/{space_uuid}/orders/'
15        response = self.post_data(endpoint, order_details)
16        return response
17    def activate_order(self, order_uuid):
18        load_dotenv()
19        space_uuid = os.getenv("SPACE_UUID")
20        endpoint = f'spaces/{space_uuid}/orders/{order_uuid}/activate/'
21        response = self.put_data(endpoint)
22        return response
23    def get_order(self, order_uuid):
24        load_dotenv()
25        space_uuid = os.getenv("SPACE_UUID")
26        endpoint = f'spaces/{space_uuid}/orders/{order_uuid}/'
27        response = self.get_data_trading(endpoint)
28        return response

As you can see, these three functionalities can be realised through requests to our API. Therefore, by making Order a subclass of the RequestHandler class, we extend the functions that we defined in the latter class, allowing us to easily (and cleanly) interact with the API.

Trading Venue Class

A Trading Venue is characterised by its MIC, which stands for ‘Market Identifier Code’, and is a way to specify the exchange on which you’d like to place your trades. We’ve written an article about our Trading Venues endpoint, which you can read here. A Trading Venue can either be open or closed. The script below shows that, in the context of Trading Venues, we want to check if a Trading Venue is open, retrieve the general opening times and determine the number of seconds until the next opening time (in the case the venue is closed).

1import datetime
2import os
3from helpers import RequestHandler
4class TradingVenue(RequestHandler):
5    _is_open: bool = False
6    @property
7    def is_open(self) -> bool:
8        mic = os.environ.get("MIC")
9        endpoint = f'venues/?mic={mic}'
10        response = self.get_data_market(endpoint)
11        return response['results'][0].get('is_open', None)
12    def check_opening_times(self):
13        mic = os.environ.get("MIC")
14        endpoint = f'venues/?mic={mic}'
15        response = self.get_data_market(endpoint)
16        return response
17    def seconds_till_tv_opens(self):
18        times_venue = self.check_opening_times()
19        today =
20        opening_days_venue = times_venue['results'][0].get('opening_days', None)
21        next_opening_day = datetime.datetime.strptime(opening_days_venue[0], '%Y-%m-%d')
22        next_opening_hour = datetime.datetime.strptime(times_venue['results'][0]['opening_hours'].get('start', None),                                            '%H:%M')
23        date_difference = next_opening_day - today
24        days = date_difference.days + 1
25        if not self.check_if_open():
26            print('Trading Venue not open')
27            time_delta = datetime.datetime.combine(
28       + timedelta(days=1), next_opening_hour.time()
29            ) -
30            print(time_delta.seconds + (days * 86400))
31            return time_delta.seconds
32        else:
33            print('Trading Venue is open')
34            return 0

Token Class

Lastly, we need a token to authenticate requests. In the .env file, we define a ‘starting’ token, however, as tokens expire, we need to be able to retrieve a new token. For that reason, the Token class has one function: retrieving a new access token (and subsequently updating the environment variable).

1import os
2from dotenv import load_dotenv
3from helpers import RequestHandler
4class Token(RequestHandler):
5    def get_new_token(self):
6        load_dotenv()
7        token_details = {
8            "client_id": os.getenv("CLIENT_ID"),
9            "client_secret": os.getenv("CLIENT_SECRET"),
10            "grant_type": "client_credentials",
11        }
12        endpoint = f'oauth2/token/'
13        response = self.get_token(endpoint, token_details)
14        os.environ['TOKEN_KEY'] = response.json().get('access_token', None)
15        return os.getenv('TOKEN_KEY')

Again, Token is a subclass of RequestHandler as we need to make a GET request to our API.

Those are all the models we need to get this specific strategy up and running. Now, let’s combine it all.

Putting it all together

After defining our helper functions, API objects and setting up our Telegram bot, it’s time to put all the pieces together.

As a first step, we define the buy_order() function, which places and activates a buy order for one share, specified by its ISIN. Our API is structured such that an order is first placed (status: inactive), then it must be activated (status: activated) and lastly it is executed (status: executed). The function continuously checks the status of the order, and when it is finally executed we use telegram-send to send a summary message about the trade. As soon as this happens, we let the script sleep for a week, at which point the next order is placed.

Then, we define our main function dollar_cost_averaging(). Here, we check if the Trading Venue is open, in the case that it is, we retrieve a new access token and call the buy_order() function with our chosen instrument. In the case that the Trading Venue is not open, we let the script sleep until the exchange reopens, at which point the trade will be placed. The use of the time.sleep() function is generally discouraged because it makes your script inaccessible for the duration of the time specified in the function. To keep the script as simple and readable as possible, we opted to use it here for illustrative purposes, but we suggest an alternative in a later section.

1from models.Order import Order
2from models.Token import Token
3from models.TradingVenue import TradingVenue
4import datetime
5import time
6import telegram_send
7def buy_order(isin: str):
8    """
9    This method places and activates a buy order for 1 unit of the specified instrument every week.
10    :param isin: isin of the instrument you want to buy
11    """
12    try:
13        placed_order = Order().place_order(
14            isin=isin,
15            valid_until=( + datetime.timedelta(hours=1)).timestamp(),
16            side="buy",
17            quantity=1,
18        )
19        order_uuid = placed_order.get('uuid')
20        activated_order = Order().activate_order(order_uuid)
21        print(activated_order)
22        while True:
23            order_summary = Order().get_order(order_uuid)
24            if order_summary.get('status') == 'executed':
25                print('executed')
26                break
27        average_price = order_summary.get('average_price')
28        amount_bought = order_summary.get('processed_quantity')
29        name_stock = order_summary.get('instrument').get('title')
30        telegram_send.send(messages=[f'Your automated trading strategy just purchased {amount_bought} share(s) of'
31                                     f' {name_stock} at €{average_price} per share.'])
32        time.sleep(604800)  # sleep for a week
33    except Exception as e:
34        print(e)
35        time.sleep(60)
36def dollar_cost_averaging():
37    while True:
38        if TradingVenue().is_open:
39            Token().get_new_token()
40            buy_order(
41                isin="LU0274208692",  # XTRACKERS MSCI WORLD SWAP
42            )
43        else:
44            time.sleep(TradingVenue().seconds_till_tv_opens())
45if __name__ == '__main__':
46    dollar_cost_averaging()

In the above code-snippet, I’ve decided to use an ETF that tracks worldwide developed equities. This way, we are further mitigating our risk. But, of course, this strategy can also be applied to individual stocks or different ETFs.

You might have noticed the line with the telegram_send(), wonder what that is all about? We still need to set up a Telegram bot to make sure the above code works. Let’s do that now.

Setting up a Telegram Bot 🤖

If you’re unfamiliar with Telegram, it’s a messaging software that supports bots. We’re going to use the Telegram API to configure a bot that automatically delivers a summary message to your smartphone once a trade has been executed.

The following section will show you how to set up Telegram notifications just like this one.

To begin, you need to register your new bot by sending /newbot to BotFather, further explanation can be found here. This yields a token, much like the token you are familiar with from the API.

To simplify the process, I’ll be using the telegram-send command-line tool, which can be installed from your terminal using pip as follows:

1$ pip install telegram-send
2$ telegram-send --configure

Your terminal will prompt you to insert your token, which connects your script to your bot. Then, after sending the given password to your new bot, you’ve successfully set up telegram-send. If this isn’t clear, full instructions can be found here.

We can then send messages using the following command:

1import telegram_send

In the following section, I will show you how to bring your Telegram bot to life (not literally).

Note: many aspects of this script can be adjusted, such as the amount, frequency and type of instruments being traded. We suggest using it as a starting point and adding your own functionalities. The above code can be found in this GitHub repo.

Possible Extensions

Ideally, you want to set up your script and forget about it — in order for this to work, it needs to be constantly running, even when your computer is powered off. For this, we suggest hosting your script in the cloud, using a platform such as Heroku, which you can conveniently connect to GitHub. We’ve covered how to use Heroku in this article, but you can also follow this or this step-by-step guide.

You’ll notice that in the case a trade is placed when the Trading Venue is closed, the trade will be placed at the next available moment: when the market reopens. However, you might want to avoid placing trades at market open as there’s usually more volatility at the beginning of a trading day. Perhaps you’d like to adjust the script such that trades are never placed before 15.30, to keep in mind NSYE/NASDAQ opening times.

You can even depart from the DCA strategy slightly — for example, by incorporating daily market data. If the price of your instrument decreases by, say, 10%, you could program your Telegram bot to notify you. Then, by interacting with the bot, you can either choose to buy additional shares, or place your trade earlier than planned. For Telegram functionality beyond simply sending messages, I suggest looking into the python-telegram-bot wrapper. Maybe you also want to add a function that notifies you if more money needs to be added to your Space.

In addition it’s worth noting that, to make the script more robust, we suggest stepping away from the time.sleep() function in Python. Why? When the script is sleeping runtime is fully blocked and you cannot wake it 💤. Instead, we suggest setting up a task queue that can be used to schedule periodic events, such as placing an order. We suggest Celery, and looking into this article to get yourself familiarised.

By setting up your own automated dollar-cost averaging strategy, you are affording yourself more flexibility than when you sign up for a standard savings plan. For example, you could set the execution time to be more flexible. You can make sure the ETF is bought at the best time during the day and not when it is most convenient for the bank. In addition, you can include multiple instruments in one savings plan, removing the need to create a separate one for each ETF, as many saving plans require you to do. The possibilities are endless, so, how would you customise this strategy?

I hope this article gave you some inspiration to get started using the API. If you haven’t signed up to yet, you can do so here. If you’d like to talk to us: leave a comment, email us or join our Slack community. We’d love to see you there!

Your lemoneer 🍋,


You might also be interested in

blog photo

Blog 38 - The market maker explained

Market Makers are crucial to provide liquidity to stock exchanges. In this blog post, we talk about what Market Makers do and why they are useful.

blog photo

5 (+1) YouTube channels for FinTech enthusiasts 

YouTube is a great way to learn about new things, including financial education or coding. Therefore, in this article we’d like to introduce you to 5 YouTube channels to level up your trading literacy.

blog photo

A short introduction to derivatives

In this article, we'd like to introduce you derivatives - they come up in finance and leave a lot of people scratching their heads, though it's totally worth it! Curious about hearing what's the difference between investing and trading, the coherence between finance and weather & why the Greeks even appear here? We'll discuss who’s using them, what they are and how they can be valued in the following.

Dive Deeper

Find more resources to get started easily

Check out our documentation to find out more about our API structure, different endpoints and specific use cases.


Join community

Join our Slack channel to actively participate in our community, ask questions to other users and stay up to date at all times.


Interested in building with us?

We are always looking for great additions to our team that help us build a brokerage infrastructure for the 21st century.

Need any help?
Ask a question in our CommunityAsk a question in our CommunityGet started with our DocumentationGet started with our DocumentationGet inspired on our BlogGet inspired on our Blog
© 2021Privacy PolicyImprint
All systems normal

As a tied agent under § 3 Sec. 2 WplG on the account and under the liability of DonauCapital Wertpapier GmbH, Passauer Str. 5, 94161 Ruderting (short: DonauCapital), GmbH offers you the receipt and transmission of orders for clients (§ 2 Sec. 2 Nr. 3 WpIG) of financial instruments according to § 3 Sec. 5 WpIG as well as brokerage of accounts.