Strategy Classes
A strategy class encapsulates one trading strategy as an object with well-defined lifecycle methods (such as on_data, on_signal and on_fill) and its own state, so the strategy logic is isolated from data handling, risk and execution, and can be tested and reused independently.
Quick answer: A strategy class encapsulates one trading strategy as an object with well-defined lifecycle methods (such as on_data, on_signal and on_fill) and its own state, so the strategy logic is isolated from data handling, risk and execution, and can be tested and reused independently.
In simple words
A strategy class is a way to package one trading strategy as a self-contained object, with methods that get called at key moments: when new data arrives, when a signal fires, and when an order is filled. Keeping the strategy's decision logic separate from the plumbing that fetches data and sends orders makes the code easier to understand, test and reuse. It mirrors how event-driven backtesters and live engines are built, so the same strategy can run in both.
Purpose
Strategy classes exist to separate the concern of deciding what to trade from the concerns of data, risk and execution, giving a testable, reusable unit that behaves identically in backtesting and live trading.
Visual explanation
Strategy Classes
The lifecycle of a strategy object: initialise, receive data, generate signals, react to fills, and finalise.
Professional explanation
Why a class, not a script
A trading strategy has state (current position, indicators, pending orders, parameters) and behaviour (how it reacts to each new piece of information). A class is the natural way to bundle that state and behaviour together, so each strategy instance carries its own configuration and running state without leaking global variables. This makes it trivial to run several strategies or several parameter sets side by side, each an independent object, and it prevents the tangled global state that plagues strategy code written as a flat script. The class becomes a clean unit you can instantiate, configure, test and drop into either a backtester or a live engine.
Lifecycle methods: the event interface
The core design is a small, well-defined set of lifecycle methods that the surrounding engine calls as events occur. A typical set is: an initialise or on_start hook to set up indicators and state; on_data (or on_bar/on_tick) called for each new market data event, where the strategy updates indicators and may decide to act; on_signal where a raw signal is turned into an intended order (often after sizing and risk checks elsewhere); on_fill (or on_order) called when an order is acknowledged, partially or fully filled, so the strategy can update its position and pending state; and an on_stop or finalise hook for end-of-session cleanup. The engine owns the loop and calls these methods; the strategy only implements the reactions. This inversion of control is what lets the identical strategy object run in a backtest (events replayed from history) and live (events from a real feed).
Separation of concerns
The strategy class should decide, not execute. It answers what and when, and hands off the how to other components: a data layer provides clean, aligned bars; a position-sizing and risk engine translate an intention into a permitted order size and veto anything that breaches limits; an execution or order-management component actually routes the order and reports back fills. Keeping these responsibilities out of the strategy class means the strategy contains only trading logic, which is what you want to test and reason about, while sizing, risk and execution are shared, hardened components reused across all strategies. A strategy that places raw broker orders directly, or that hard-codes its position size, has entangled concerns and is far harder to test and to keep safe.
State management and idempotency
Because lifecycle methods are called repeatedly, state discipline is critical. The class must track its position and pending orders accurately, avoid acting twice on the same signal, and remain consistent when a fill arrives out of order or an order is rejected. A common pattern is to keep an explicit state machine (flat, entering, long, exiting) and to make signal handling idempotent so a duplicate on_data call cannot double the position. Storing all mutable state on the instance (not in module globals) also makes the strategy resettable between backtest runs and safe to run in multiple instances.
Testability
The payoff of this structure is testability. Because the strategy is a class with a defined event interface and no hidden dependencies, you can unit-test it by constructing an instance and feeding it a scripted sequence of synthetic data and fill events, then asserting on the intentions it emits, with no broker, no network and no real data required. You can replay a deterministic historical sequence and check the exact orders produced (a form of golden-master or replay test), and you can inject a fake execution component to verify how the strategy reacts to partial fills or rejections. This is only possible because concerns are separated; a strategy tangled with live API calls cannot be tested this way.
Reuse across backtest and live
The strongest argument for the pattern is a single code path. If backtesting and live trading both drive the same strategy class through the same lifecycle methods, differing only in where events come from (replayed history versus a live feed) and where orders go (a simulator versus a broker), then what you tested is what you run. This eliminates a whole category of discrepancies where a strategy behaves differently in backtest and live because it was re-implemented for each. Frameworks like Backtrader and Zipline are built exactly this way, with a base Strategy class and next/handle_data style hooks that users override.
Practical example
Illustrative example (Indian market)
Consider a Nifty mean-reversion strategy written as a class. In initialise it sets its parameters (lookback, entry z-score, one lot of 75) and creates a rolling window. Its on_data method is called for each one-minute bar, updates the window, computes a z-score, and if the score crosses the entry threshold while the strategy is flat, it emits an intended entry signal, it does not place a broker order directly. The engine passes that intention through the shared position-sizing and risk engine, which may cut or veto it, then to execution. When the order fills, on_fill is called and the class updates its position from flat to long. To test it, you instantiate the class, feed a hand-built sequence of bars that should trigger exactly one entry, and assert that precisely one entry intention was emitted and the position state is correct, with no broker involved. This is an illustrative structure, not a strategy recommendation.
In an NSE F&O context, the same strategy class can be replayed over historical Bank Nifty minute bars in a backtest and then run live during the 09:15 to 15:30 session, with only the event source and order sink swapped. Keeping lot-size (say 15 or 75 depending on the contract), STT/brokerage cost modelling and daily-loss limits in the shared risk layer, not in the strategy class, means one hardened risk component protects every strategy.
Advantages
- Encapsulates strategy state and logic in a clean, reusable, instantiable unit
- Lifecycle methods give a clear event interface shared by backtest and live engines
- Separation of concerns keeps strategy code focused on decisions, not plumbing
- Highly testable: feed synthetic events and assert on emitted intentions
- Multiple strategies or parameter sets run as independent objects without shared globals
Limitations
- Requires disciplined design; a poorly structured class re-tangles concerns
- The lifecycle abstraction adds a learning curve over a simple script
- State and idempotency bugs are easy to introduce if the state machine is sloppy
- Over-abstraction (too many hooks and layers) can obscure simple logic
- The pattern helps structure but does not by itself make a strategy profitable or correct
Common mistakes
- Placing raw broker orders directly inside the strategy class, entangling execution with decision logic and breaking testability
- Hard-coding position size in the strategy instead of delegating to a shared sizing and risk engine
- Storing strategy state in module-level globals, so instances interfere and backtests cannot reset cleanly
- Acting on the same signal twice because signal handling is not idempotent, doubling the intended position
- Mishandling out-of-order or partial fills in on_fill, leaving the tracked position out of sync with reality
- Re-implementing the strategy separately for backtest and live, so the two behave differently
Professional usage
Professional and framework-grade systems universally structure strategies as classes with a lifecycle interface. A base Strategy class defines hooks (initialise, on_data/next, on_order, on_fill, on_stop) and the engine, whether backtest or live, drives instances through them, guaranteeing a single code path. Sizing, risk and execution are separate, shared, heavily tested components, so the strategy class contains only trading logic. Teams keep all mutable state on the instance, model the position as an explicit state machine, make signal handling idempotent, and unit-test each strategy by replaying scripted event sequences before it ever sees capital.
Key takeaways
- A strategy class bundles one strategy's state and logic behind lifecycle methods like on_data, on_signal and on_fill
- Separate the strategy's decisions from data, sizing, risk and execution concerns
- The engine calls the lifecycle methods; the strategy only implements reactions (inversion of control)
- This structure makes strategies unit-testable with synthetic events and no broker
- One strategy class driven identically in backtest and live removes a whole class of discrepancies
Frequently asked questions
What is a strategy class in trading?
What are lifecycle methods in a trading strategy?
Why should strategy logic be separated from execution?
How does structuring a strategy as a class improve testability?
What is separation of concerns in trading code?
What is the on_fill method for?
Why use the same strategy class for backtesting and live trading?
What is inversion of control in a strategy engine?
What state should a strategy class hold?
How do I make signal handling idempotent?
Can I run multiple strategies at once with this pattern?
Is a strategy class the same as a backtesting framework?
Does a good strategy class make a strategy profitable?
Voice search & related questions
Natural-language questions people ask about Strategy Classes.
What is a strategy class?
Why put a trading strategy in a class?
What are the main methods in a strategy class?
How does a strategy class make testing easier?
Why keep order placement out of the strategy?
Can the same strategy run in backtest and live?
Sources & references
Last reviewed 11 July 2026. Educational content only — not investment advice. Markets and rules change; verify current conventions with SEBI, NSE/BSE and your broker.