Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 1

Designing Core Trading System Interfaces

Recommended resources

Hello! Welcome to the first lesson of our course on building a trading system in Python.

Our goal is to construct a robust framework for executing credit spread strategies. The first, and most critical, step in building any complex software is to lay a solid architectural foundation. This lesson is dedicated to exactly that. We will define the "contracts," or interfaces, for the three most important components of our system:

  • DataHandler: The component responsible for sourcing all market data.
  • Strategy: The component that contains the trading logic and generates signals.
  • ExecutionHandler: The component that manages communication with the brokerage to execute trades.

By the end of this lesson, you will have defined these core interfaces using Python's Abstract Base Classes (ABCs). This approach enforces a clean separation of concerns and ensures our system is modular and extensible from the very beginning—a key step toward building a "foolproof" system.

Interfaces and Abstract Base Classes (ABCs)

In software engineering, an interface is like a contract. It defines a set of methods that a class must implement, without specifying how they are implemented. Think of a standard electrical wall socket: it provides an interface. Any appliance with a plug that conforms to that standard can connect to it, regardless of whether it's a lamp or a laptop. The socket doesn't care about the appliance's internal workings, only that it connects correctly.

In Python, one of the primary ways to define such an interface is by using Abstract Base Classes, or ABCs. An ABC is a special type of class that you cannot create an object from directly. Its purpose is to be inherited by other classes, forcing them to implement the methods defined in the ABC's "contract."

To get a solid grasp of what abstract classes are and how they work in Python, let's start with this quick and clear video.

Learn Python ABSTRACT CLASSES in 7 minutes! 👻

This video from the Bro Code channel provides a concise and practical introduction to Abstract Base Classes in Python.

Please watch the entire video (about 7 minutes). Pay close attention to the abc module, the @abstractmethod decorator, and the TypeError that occurs when you try to instantiate an abstract class or a subclass that hasn't implemented the required methods.

To summarize the key points from the video:

  • An ABC is created by inheriting from the ABC class from the abc module.
  • It defines one or more abstract methods using the @abstractmethod decorator. These methods have a signature but no implementation (pass or a raise NotImplementedError in the body).
  • You cannot create an instance of an ABC.
  • Any concrete (i.e., non-abstract) subclass must implement all the abstract methods of its parent ABC. This is how the "contract" is enforced.

This concept is visualized well in the following diagram, where AutoMobile is an abstract class that defines a contract for concrete classes like Car and Truck.

Abstract Class and Inheritance Example
This diagram illustrates the principle of an abstract class (`AutoMobile`) serving as a blueprint. It mandates that any subclass, such as `Car` or `Truck`, must provide its own implementation for methods like `start()`, `accelerate()`, and `stop()`.

Designing the Core Trading Framework Interfaces

Now, let's apply this concept to design the skeleton of our trading framework. We will define the interfaces for our three core components. The design is inspired by common architectures used in professional event-driven trading systems, such as those described in the articles "How I Built an Event-Driven Backtesting Engine in Python" and "Event-Driven Backtester in Python".

1. The DataHandler Interface

The DataHandler's role is to be the sole provider of market data to the rest of the system. This is a crucial abstraction. It means our strategy logic won't need to know or care whether the data is coming from a live API feed, a database, or a local CSV file.

We will define an interface that any data handler, regardless of its source, must adhere to.

from abc import ABC, abstractmethod
from typing import List, Dict, Any

class DataHandler(ABC):
    """
    The DataHandler is an abstract base class providing an interface for all
    subsequent data handlers (both live and historical).

    The goal of a (derived) DataHandler object is to output a generated
    set of bars (OHLCV) for each symbol on a "heartbeat".
    """

    @abstractmethod
    def get_latest_bars(self, symbol: str, N: int = 1) -> List[Dict[str, Any]]:
        """
        Returns the last N bars of market data for the given symbol.
        """
        raise NotImplementedError("Should implement get_latest_bars()")

    @abstractmethod
    def update_bars(self) -> None:
        """
        Pushes the latest bar to the internal data structure for all symbols.
        In an event-driven system, this would also generate a MarketEvent.
        """
        raise NotImplementedError("Should implement update_bars()")

Explanation of Methods:

  • get_latest_bars(symbol, N): This method is essential for providing historical context. A strategy might need the last 10 bars of data to calculate a moving average or other technical indicators before making a decision.
  • update_bars(): This method drives the data flow. When called, it is responsible for fetching the most recent data from its source (e.g., the next row in a file or a new tick from an API) and making it available via get_latest_bars.

2. The Strategy Interface

The Strategy is the intellectual core of the system. It contains the logic that analyzes market data and generates trading signals (e.g., "enter bull put spread," "close position"). A critical design principle is to keep the strategy independent of portfolio state (like current positions or cash). It should be a pure-function-like component that transforms data into signals.

from abc import ABC, abstractmethod

# The `Event` class will be defined in a later lesson.
# For now, it's a placeholder for data passed between components.
class Event:
    pass

class MarketEvent(Event):
    pass


class Strategy(ABC):
    """
    The Strategy is an abstract base class providing an interface for
    all subsequent strategy objects.

    Its role is to generate SignalEvents for a particular symbol based
    on market data.
    """

    @abstractmethod
    def calculate_signals(self, event: MarketEvent) -> None:
        """
        Calculates a list of signals based on market data.
        In an event-driven system, this is triggered by a MarketEvent.
        """
        raise NotImplementedError("Should implement calculate_signals()")

Explanation of Method:

  • calculate_signals(event): This is the heart of the strategy. It gets triggered when new market data is available (represented by MarketEvent). Inside this method, the logic will analyze the data (using DataHandler.get_latest_bars()) and decide whether to generate a signal.

3. The ExecutionHandler Interface

The ExecutionHandler is responsible for the practicalities of trading. It takes an OrderEvent (which describes a desired trade) from the system and handles the process of submitting it to the brokerage. This interface allows us to easily switch between a simulated handler for backtesting (which mimics fills and costs) and a live handler that connects to a real broker API.

from abc import ABC, abstractmethod

class OrderEvent(Event):
    pass


class ExecutionHandler(ABC):
    """
    The ExecutionHandler abstract class handles the interaction between
    OrderEvents and the ultimate FillEvents that occur in the market.

    It can be used to simulate execution or to live execute orders.
    """

    @abstractmethod
    def execute_order(self, event: OrderEvent) -> None:
        """
        Takes an OrderEvent and executes it, producing a FillEvent
        that confirms the trade.
        """
        raise NotImplementedError("Should implement execute_order()")

Explanation of Method:

  • execute_order(event): This method takes the detailed order, interacts with the brokerage system, and (in a complete system) would receive a confirmation that the order has been filled, which it then reports back.
Test your understanding!

Imagine you want to get your options data from a new provider, OptionsDataPro, which has its own Python API. To integrate this into your framework, which of the three interfaces (DataHandler, Strategy, or ExecutionHandler) would you need to implement? And why does this design prevent you from having to modify your BullPutSpread strategy code?

Show answer

You would need to create a new class, let's call it OptionsDataProHandler, that inherits from the DataHandler abstract base class.

You would then implement the get_latest_bars() and update_bars() methods within this new class, using the specific functions from the OptionsDataPro API to fetch the data.

This design is powerful because your BullPutSpread strategy code only interacts with the DataHandler interface. It calls get_latest_bars() without any knowledge of where the data comes from. As long as the OptionsDataProHandler fulfills the DataHandler contract, you can swap it in for any other data handler, and the strategy code will continue to work without any changes. This is the principle of "loose coupling."


The Big Picture: An Event-Driven Flow

So, how do these components work together? They will communicate through an event-driven architecture. Instead of calling each other directly, components will place "Event" objects (like MarketEvent, OrderEvent) onto a central queue. A main loop processes these events one by one, passing them to the relevant component.

This diagram illustrates the general concept:

Event-Driven Architecture Diagram
This diagram shows the flow in an event-driven system. An event source generates an event, which is placed in a queue. An event loop retrieves the event and passes it to the correct handler for processing.

In our system, the flow will look like this:

  1. The DataHandler's update_bars method gets new data and generates a MarketEvent.
  2. The main loop passes this MarketEvent to the Strategy's calculate_signals method.
  3. The Strategy might generate a SignalEvent (e.g., "go long").
  4. A Portfolio component (which we'll build later) processes the signal and creates a concrete OrderEvent.
  5. The main loop passes the OrderEvent to the ExecutionHandler's execute_order method, which places the trade.

By defining these clear interfaces today, we've paved the way for building each piece of this complex system independently.


Conclusion

In this foundational lesson, we established the architectural blueprint for our trading system.

Key Takeaways:

  • Interfaces as Contracts: Interfaces define a contract that classes must follow, ensuring components are interchangeable and the system is modular.
  • Abstract Base Classes (ABCs): In Python, ABCs are the primary tool for creating interfaces. They use the @abstractmethod decorator to enforce the implementation of required methods in subclasses.
  • Core Components: We have defined the three core interfaces for our trading framework:
    • DataHandler: For abstracting the data source.
    • Strategy: For encapsulating trading logic.
    • ExecutionHandler: For managing order execution.

This structured, interface-based design is a crucial step toward building a robust and maintainable system.

Preview of the Next Lesson:
Now that we have defined the Strategy interface, our next lesson will focus on the Strategy design pattern. This powerful pattern will allow us to encapsulate our specific credit spread strategies (BullPutSpread and BearCallSpread) into separate, interchangeable classes that all conform to the Strategy interface we defined today. This will make it trivial to switch between different strategies or add new ones in the future.