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
ABCclass from theabcmodule. - It defines one or more abstract methods using the
@abstractmethoddecorator. These methods have a signature but no implementation (passor araise NotImplementedErrorin 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.

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 viaget_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 byMarketEvent). Inside this method, the logic will analyze the data (usingDataHandler.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:

In our system, the flow will look like this:
- The
DataHandler'supdate_barsmethod gets new data and generates aMarketEvent. - The main loop passes this
MarketEventto theStrategy'scalculate_signalsmethod. - The
Strategymight generate aSignalEvent(e.g., "go long"). - A
Portfoliocomponent (which we'll build later) processes the signal and creates a concreteOrderEvent. - The main loop passes the
OrderEventto theExecutionHandler'sexecute_ordermethod, 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
@abstractmethoddecorator 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.