Simple Yet Effective Architecture Patterns for Algorithmic Trading

In this thread, I'll show you how to develop trading software that is easy to maintain and update, while making it simple to collect statistics and track bugs.

1/ Events-Driven Architecture

At the core of this architecture are EVENTS. Every significant action, such as receiving data or processing results, will generate events. This approach allows:

  • Clear tracking of system activities

  • Standardized interaction between system components

  • Easy addition of new event handlers

Example of an event:

class BalanceUpdate(Event):
    def __init__(self, timestamp, symbol, amount) -> None:
        self.type = 'BALANCEUPDATE'
        self.timestamp = timestamp
        self.symbol = symbol
        self.amount = float(amount)

In our trading system, we need balance updates via websocket or REST API. Other updates include price changes, order info, and internal system events. Each module handles a specific function and generates events like BalanceUpdate.

2/ Data Storage

It's most efficient to store all data in a single repository. This includes price updates, balances, order data, signals, etc. Here's the workflow: The data module requests from a remote server, processes the response, and generates an event. Event handlers then save these data to the repository or trigger other handlers.

class Balances:
    def __init__(self):
        self.data = {}
        self.total = 0
        self.usd_values = {}

    def update(self, event):
        self.data[event.symbol] = event.amount

    def read(self):
        return self.data

Storing data in memory is the simplest and fastest method, suitable for most trading algorithms that don't require permanent data storage. For more complex needs, use Redis for an in-memory storage interface, especially when accessing data from other microservices.

3/ Configuration

Separating settings into a config file is good practice for trading software and any other system. This centralizes settings, making changes easy to manage.

Example config:

[general]
username = my_user_name

[trade]
markets = ['BTC/USDT', 'ETH/USDT']

Read config with configparser:

def read_config(self):
    config_object = ConfigParser()
    config_object.read('config.ini')
    settings = {sect: dict(config_object.items(sect)) for sect in config_object.sections()}
    settings['trade']['markets'] = eval(settings['trade']['markets']) #string to dict
    return settings

4/ Event Processing Loop

Consider this the main file coordinating all system elements:

  • Read settings from config

  • Initialize system modules with settings

  • Start the event processing loop

  • Launch tasks like balance updates every minute or continuous websocket listening

Example balance update event handling:

if event.type == 'BALANCEUPDATE':
    print('BALANCE UPDATE')
    self.data_storage.balances.update(event)
    self.data_storage.calc_usd_value()

In summary, we've covered several components of a trading system without delving into specific strategies: events, data storage, config, and event loop. These practices are essential for developing any trading algorithm. In the next article, I'll discuss strategy modules, portfolio management, risk management, and order execution.

All the code will be open-source and available upon the beta release of the service, developed in collaboration with aspis.finance —a DeFi platform that makes trader-investor relationships transparent through smart contracts.

Got questions? Reach out on TG @JungleSven.