Code structureIntermediate

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.

Strategy LifecycleHypothesisBuildBacktestValidateDeployMonitorDecay /Retireiterate

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?
It is an object-oriented way to package one trading strategy, bundling its state (position, indicators, parameters) with behaviour exposed as lifecycle methods that an engine calls as events occur. It isolates trading logic from data, risk and execution, making the strategy reusable and testable.
What are lifecycle methods in a trading strategy?
They are the hooks the engine calls at defined moments: an initialise step, on_data (or on_bar/on_tick) for each market event, on_signal to turn a signal into an intended order, on_fill when an order is executed, and a finalise step. The strategy implements these reactions while the engine owns the loop.
Why should strategy logic be separated from execution?
So the strategy contains only decisions (what and when) while shared, hardened components handle sizing, risk and order routing (the how). This separation makes the strategy testable in isolation, lets one risk engine protect every strategy, and prevents dangerous entanglement of trading logic with live API calls.
How does structuring a strategy as a class improve testability?
Because the class exposes a clear event interface and has no hidden dependencies, you can construct an instance, feed it scripted synthetic data and fill events, and assert on the intentions it emits, with no broker or network. A strategy tangled with live API calls cannot be tested this way.
What is separation of concerns in trading code?
It is the principle that each component has one responsibility: the strategy decides, the data layer supplies clean data, the risk engine enforces limits, and the execution layer routes orders. Keeping these apart makes each simpler to test, reuse and reason about.
What is the on_fill method for?
It is called when an order is acknowledged or filled (fully or partially) so the strategy can update its tracked position and pending-order state. Handling it correctly, including partial and out-of-order fills, keeps the strategy's view of its position consistent with the broker's.
Why use the same strategy class for backtesting and live trading?
So that what you tested is what you run. If both modes drive the identical class through the same lifecycle methods, differing only in the event source and order sink, you eliminate discrepancies caused by re-implementing the strategy separately for each mode.
What is inversion of control in a strategy engine?
It means the engine, not the strategy, owns the main loop and calls the strategy's lifecycle methods when events occur, rather than the strategy calling the engine. This lets the same strategy object be driven by a backtest engine or a live engine without changing the strategy code.
What state should a strategy class hold?
Its parameters, indicators or rolling windows, current position, pending orders and an explicit position state (for example flat, entering, long, exiting). Keeping all mutable state on the instance rather than in globals lets multiple instances run independently and lets backtests reset cleanly.
How do I make signal handling idempotent?
Track the current state and pending intentions so that reprocessing the same event, or a duplicate call, cannot trigger the action twice. For example, only emit an entry if the strategy is flat and has no pending entry, so a repeated on_data cannot double the position.
Can I run multiple strategies at once with this pattern?
Yes, that is a key benefit. Because each strategy is an independent object carrying its own state, you can instantiate many strategies or many parameter sets and drive them all through the same engine, with a shared risk and execution layer managing the combined orders.
Is a strategy class the same as a backtesting framework?
No, but frameworks like Backtrader and Zipline are built around the strategy-class pattern: they provide a base Strategy class with lifecycle hooks you override. The pattern is the design idea; a framework is one implementation of an engine that drives such classes.
Does a good strategy class make a strategy profitable?
No. Good structure makes a strategy testable, maintainable and safe to run, but it says nothing about whether the underlying trading idea has an edge. Structure is necessary engineering discipline, not a source of profit.

Voice search & related questions

Natural-language questions people ask about Strategy Classes.

What is a strategy class?
It is a way to package one trading strategy as an object, with methods that run when new data arrives, when a signal fires, and when an order fills, keeping the trading logic separate from the plumbing.
Why put a trading strategy in a class?
Because it bundles the strategy's state and logic in one reusable, testable unit and keeps it separate from data handling, risk and order placement, which makes the code easier to trust.
What are the main methods in a strategy class?
Usually an initialise step, an on-data method for each new bar or tick, an on-signal step, and an on-fill method for when orders execute, plus a cleanup step at the end.
How does a strategy class make testing easier?
You can create the strategy on its own, feed it fake data and fake fills, and check the orders it decides to make, all without touching a real broker.
Why keep order placement out of the strategy?
So the strategy only decides what to trade, while shared risk and execution components handle how, which keeps the strategy simple to test and lets one risk engine protect every strategy.
Can the same strategy run in backtest and live?
Yes. If the same strategy class is driven the same way in both, differing only in where data and orders come from and go to, then what you tested is exactly what you run.

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.

    Educational content only — not investment advice. Examples use illustrative numbers and simplified models. Algorithmic trading and derivatives involve substantial risk. See our Risk Disclosure and SEBI Disclaimer.