Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Run

Overview

The core of the system is the run() function, which connects all components in a streaming event loop. The run is used for every stage in the 4 stages, from back-testing to live-trading.

Each of these five components in the run has its own responsibilities:

ComponentResponsibility
FeedProvides (market) data events
StrategyGenerates trading signals from events
TraderConverts signals into orders (risk/sizing)
BrokerExecutes orders, maintains account state
JournalLogs/tracks every step (read-only)

Each component is implemented an Abstract Base Class with pluggable implementations, making every part of the pipeline independently swappable.

Step-by-step

It is helpful to understand some of the details of the run loop.

for event in feed.play(...):
    account = broker.sync(event)                # 1 syncs and updates the account
    signals = strategy.create_signals(event)    # 2 generate signals from market data
    orders = trader.create_orders(signals, ...) # 3 apply risk rules, create orders
    broker.place_orders(orders)                 # 4 place orders at broker
    journal.track(...)                          # 5 record metrics (optional)
  1. broker.sync(event) — Sync with the state of the underlying broker. Returns an updated Account object which reflects the latest state and market data. Orders and positions that are closed, are not included in the returned account object.

  2. strategy.create_signals(event) — The strategy examines the event’s price data and returns a list of Signal objects. Each signal has an asset, a rating (typically -1.0 to 1.0), and a type (ENTRY, EXIT, or ENTRY_EXIT). Strategies are pure decision-makers — they know nothing about cash, positions, or risk.

  3. trader.create_orders(signals, event, account) — The trader applies risk management rules (position sizing, shorting constraints, order limits) and converts signals into concrete Order objects. Unlike strategies, traders have full access to the Account (cash, positions, buying power).

  4. broker.place_orders(orders) — New orders are submitted to the broker. In SimBroker, they are stored and only evaluated for execution when the next event arrives.

  5. journal.track(...) — Optional logging and metrics collection. Journals are passive observers that never modify state.

The run function also has many defaults for its parameters in case they are not provided:

Basic Backtest

A simple back test that iterates over all the historic data in the feed, just requires a few lines of code.

buying power : 2,018,593@USD
cash         : 2,018,593@USD
equity       : 2,018,593@USD
positions    : none
trades       : 188
mkt value    : 0@USD
orders       : none
last update  : 2026-09-30 04:00:00+00:00

This works because run() provides sensible defaults: SimBroker (USD 1M deposit, 0% slippage) and SimpleTrader.

Custom Backtest

The following snippets shows how to override many of the default settings.

Walk Forward

Walk-forward analysis is a backtesting technique that mimics real-world trading by splitting historical data into successive periods. Below is very simple example of a walk forward that provides insights into the performance in different timeframes.

[2020-01-02 ― 2021-05-08> equity=1359561@USD
[2021-05-08 ― 2022-09-13> equity=972719@USD
[2022-09-13 ― 2024-01-18> equity=1286717@USD
[2024-01-18 ― 2025-05-25> equity=884136@USD
[2025-05-25 ― 2026-09-30] equity=1122342@USD

Often a walk forward is used in combination with hyperparameter tuning. This is known as Walk-Forward Optimization (WFO). The idea is:

  1. Split the historical data into a sequence of time windows (e.g. 5 periods).

  2. For each window, use the current window as the in-sample (training) period to find the best parameters.

  3. Test those parameters on the next window, the out-of-sample (testing) period.

  4. Move forward one window and repeat.

This approach helps detect overfitting: if a parameter set performs well in-sample but poorly out-of-sample, the strategy likely doesn’t generalize. By measuring performance only on unseen data, you get a more realistic estimate of how the strategy would have performed in production.

In practice, you might also track metrics across all out-of-sample periods (e.g. average Sharpe ratio, win rate) rather than just the final equity value, giving a fuller picture of robustness.

param=(20, 31) training=1,423,592 testing=897,870
param=(3, 5) training=1,196,387 testing=1,119,791
param=(20, 31) training=1,292,065 testing=1,007,585
param=(3, 5) training=1,067,922 testing=1,314,897

Multi-run

A Multi-run samples a number of random timeframes and then runs a back test on each of them.

min=863494, max=1373082

Live and paper-trade run

A live or paper-trade run is only different from a back test in the implementation of two of the components selected.

Instead of the SimBroker a real broker is selected and instead of a historic data feed, a live datafeed is used.

import os
from dotenv import load_dotenv
from roboquant.third_party.alpaca import AlpacaLiveFeed, AlpacaBroker

load_dotenv()

# Setup the real broker
api_key =  os.environ["ALPACA_API_KEY"]
secret_key = os.environ["ALPACA_SECRET"]
broker = AlpacaBroker(api_key, secret_key)

# Setup the live feed
alpaca_feed = AlpacaLiveFeed(api_key, secret_key, market="iex")
symbols = ["TSLA", "MSFT", "NVDA", "AMD", "AAPL"]
alpaca_feed.subscribe_trades(*symbols)

# Run a strategy
strategy = rq.strategies.EMACrossover(13, 26)
timeframe = rq.Timeframe.next("120 min")
account = rq.run(feed, strategy, broker=broker, timeframe=timeframe)

Early stopping

It is possible to stop a run before all the events are handled. You do so by having one of the components call the stop_run() function.

For example a custom Journal could track some metrics and based on the results invoke the stop_run() function. See also journal

Under the hood it will throw a special type of exception that is handled gracefully within the run loop.