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.