Skip to content
Course contents
From backtest to live strategy

A strategy that runs itself

In a backtest the strategy reads a whole history at once; live, it must react to data as it arrives and decide to act or wait each time. This chapter defines a strategy as a small program with a clear interface, given the latest data return the orders you want, and runs one against the simulated broker so an idea becomes an actual running loop.

10 min readChapter 14 of 28
What you will learn
  • Express a strategy as an object with a defined interface (data in, intended orders out)
  • Run the strategy in a loop that reacts to each new bar
  • Connect the strategy to the broker through the order path, not directly

In Python for Trading, your strategy was a handful of lines that ran over a whole DataFrame at once and produced a column of signals. That is perfect for testing an idea on history, but it is not how a strategy trades in real life. Live, the future has not happened yet. The strategy sees one bar, then waits, then sees the next, and at each step it must decide: act, or do nothing. This chapter turns the idea into that shape, a small program that runs itself, reacting to each new bar as it arrives.

A strategy is an object with one question

Live, a strategy is a small loop: given the latest data, decide the orders you want, send them, and wait for the next data.
Live, a strategy is a small loop: given the latest data, decide the orders you want, send them, and wait for the next data.

The cleanest way to write a strategy that can run live is as a small object with a single method the loop calls on every bar. You hand it the data so far, and it hands back the orders it wants. Nothing more. It does not fetch data, it does not talk to the broker, and it does not know whether it is being backtested or run live. It answers one question: given what I now know, what orders do I want. Here is the crossover from Python for Trading written that way.

ExampleA strategy as an object: data in, intended orders outch14/strategy.py
# A strategy is a small object with one job: given the price history so far,
# return the orders it wants. This crossover goes long when the fast moving
# average is above the slow one, and flat otherwise. It knows nothing about the
# broker; the runner connects the two. The same object can drive a backtest, a
# forward test, or (much later, with care) a live account.
class Crossover:
    def __init__(self, fast=20, slow=50, quantity=10):
        self.fast = fast
        self.slow = slow
        self.quantity = quantity
        self.position = 0                # 0 = flat, 1 = long

    def on_bar(self, closes):
        """closes: the price history up to and including the latest bar.
        Returns a list of (side, quantity) orders it wants placed."""
        if len(closes) < self.slow:
            return []                    # not enough history to decide yet
        fast_ma = sum(closes[-self.fast:]) / self.fast
        slow_ma = sum(closes[-self.slow:]) / self.slow
        want = 1 if fast_ma > slow_ma else 0
        if want == self.position:
            return []                    # no change means no order
        side = "BUY" if want > self.position else "SELL"
        self.position = want
        return [(side, self.quantity)]

Read its shape. on_bar receives the price history up to the latest bar. If there is not enough history yet, it wants nothing. Otherwise it computes the two moving averages, decides whether it wants to be long or flat, and if that differs from its current position, it returns a single order to make the change. The strategy holds one piece of memory, its current position, and it returns intentions, not fills. That separation is the whole point: the strategy decides, and something else executes.

The loop that runs it

A strategy object does nothing on its own. It needs a loop to feed it bars and carry out its orders. That loop is the live heartbeat from the architecture chapter, made real. Each time a bar arrives, the loop updates the market, asks the strategy what it wants, and places any orders through the broker. Here is that loop, driving the strategy over the sample data against the simulated broker.

ExampleA loop that feeds the strategy bars and places its ordersch14/run_strategy.py
# Turning the strategy into a program that runs itself. A loop feeds the strategy
# one bar at a time, and whenever the strategy asks for an order, the loop places
# it through the broker. This is the shape of a live trading loop, here driven by
# historical bars and the simulated broker.
import pandas as pd
from paper_broker import PaperBroker
from strategy import Crossover

df = pd.read_csv("sample_prices.csv", parse_dates=["Date"], index_col="Date")
closes = df["Close"].tolist()
dates = df.index

broker = PaperBroker(cash=1_000_000, prices={"RELIANCE": closes[0]})
strategy = Crossover(fast=20, slow=50, quantity=10)

print("The strategy runs itself, one bar at a time:")
trades = 0
for i in range(len(closes)):
    price = closes[i]
    broker.feed_price("RELIANCE", price)                 # a new bar arrives
    for side, quantity in strategy.on_bar(closes[:i + 1]):
        broker.place_order("RELIANCE", side, quantity, "MARKET")
        trades += 1
        print(f"  {dates[i].date()}  {side:<4} {quantity} @ {price:.2f}")

print(f"\nTrades placed: {trades}")
print("Final position:", broker.get_positions())
print("Final funds:   ", broker.get_funds())
Output
The strategy runs itself, one bar at a time:
  2024-04-18  BUY  10 @ 1307.39
  2024-05-23  SELL 10 @ 1235.59
  2024-06-24  BUY  10 @ 1345.35
  2024-07-26  SELL 10 @ 1269.63
  2024-08-21  BUY  10 @ 1369.03

Trades placed: 5
Final position: [{'symbol': 'RELIANCE', 'quantity': 10, 'avg_price': 1369.03}]
Final funds:    {'available_cash': 984834.5}

Watch it run. Bar by bar, the strategy stays quiet until a crossover happens, then asks for an order, which the loop places. Over the sample it makes 5 trades, buying and selling as the fast average crosses the slow one, and it ends holding a long position. This is a strategy that runs itself: no human deciding each trade, just the loop, the strategy, and the broker. And notice, with the honesty this course insists on, that the trades are not pretty. It bought at 1,307 and sold at 1,235, bought at 1,345 and sold at 1,269, losses you can read straight off the output. A running strategy is not a winning strategy, a point the next chapters sharpen.

The strategy never touches the broker

One detail is worth dwelling on. The strategy returns orders; the loop places them. The strategy never calls the broker itself. This is the separation from the architecture chapter, and in the parts still to come it is what lets you slip a risk gate between the strategy's wish and the broker's action, so no strategy bug can bypass your limits. A strategy that reached for the broker directly would be a strategy you could not safely restrain. Keep the decider and the executor apart.

What to carry forward

A live strategy is written as a small object with one method: given the price history so far, return the orders it wants, holding only its own position as memory and never touching the broker. A loop feeds it each new bar and places its orders through the broker, which is what "runs itself" means, and you watched the crossover do exactly that over the sample, trades and losses on show. Keeping the strategy separate from execution is what will let you put a risk gate in between later. The next chapter asks a sharp question about this loop: does running the strategy bar by bar give the same answer as the all-at-once backtest? It must, and showing that it does is how you trust a live engine.