Skip to content

Execution Methods

This guide covers all the ways you can run trading strategies in RustyBT, helping you choose the right execution method for your workflow.

Overview

RustyBT offers multiple execution methods to fit different development workflows:

Method Best For Pros Cons
CLI Quick testing, production scripts Simple, scriptable Less IDE integration
Python API Development, debugging IDE support, Pythonic Requires code changes
Jupyter Notebooks Research, exploration Interactive, visual Not for production
Class-based Complex strategies, Pipeline API Organized, reusable More boilerplate
Function-based Simple strategies, quick tests Minimal code Limited structure

CLI Execution

Basic Usage

The command-line interface provides the simplest way to run strategies:

rustybt run -f my_strategy.py -b yfinance-profiling --start 2020-01-01 --end 2023-12-31

CLI Options

rustybt run [OPTIONS]

Options:
  -f, --file PATH              Strategy file (required)
  -b, --bundle TEXT            Data bundle name (default: quantopian-quandl)
  --start DATE                 Start date (YYYY-MM-DD)
  --end DATE                   End date (YYYY-MM-DD)
  --capital-base FLOAT         Starting capital (default: 10000)
  --data-frequency TEXT        'daily' or 'minute' (default: daily)
  -o, --output PATH            Output file for results
  --help                       Show help message

Complete CLI Example

# momentum_strategy.py
from rustybt.api import order_target_percent, symbol, record

def initialize(context):
    """Initialize strategy."""
    context.assets = [symbol('AAPL'), symbol('MSFT'), symbol('GOOGL')]
    context.lookback = 20

def handle_data(context, data):
    """Execute on each bar."""
    for asset in context.assets:
        # Calculate momentum
        prices = data.history(asset, 'price', context.lookback, '1d')
        momentum = (prices[-1] - prices[0]) / prices[0]

        # Trade on momentum signal
        if momentum > 0.02:  # 2% threshold
            order_target_percent(asset, 0.33)
        elif momentum < -0.02:
            order_target_percent(asset, 0)

        record(**{asset.symbol: prices[-1], f"{asset.symbol}_momentum": momentum})

Run with:

rustybt run -f momentum_strategy.py -b yfinance-profiling --start 2020-01-01 --end 2023-12-31 --capital-base 100000

Advantages

✅ Simple one-line execution ✅ Easy to script and automate ✅ No code modifications needed ✅ Works with cron jobs and schedulers

Limitations

❌ Limited IDE debugging support ❌ Results printed to console (need -o for file output) ❌ Less flexible for programmatic access


Python API Execution

Basic Usage

The Python API provides a more Pythonic way to run strategies with better IDE integration:

from rustybt.utils.run_algo import run_algorithm
import pandas as pd

result = run_algorithm(
    initialize=initialize,
    handle_data=handle_data,
    bundle='yfinance-profiling',
    start=pd.Timestamp('2020-01-01'),
    end=pd.Timestamp('2023-12-31'),
    capital_base=10000
)

Function Signature

def run_algorithm(
    start: datetime,
    end: datetime,
    initialize: callable,
    capital_base: float,
    handle_data: callable = None,
    before_trading_start: callable = None,
    analyze: callable = None,
    data_frequency: str = 'daily',
    bundle: str = 'quantopian-quandl',
    bundle_timestamp: datetime = None,
    trading_calendar: TradingCalendar = None,
    metrics_set: str = 'default',
    benchmark_returns: pd.Series = None,
    blotter: str = 'default'
) -> pd.DataFrame

Complete Python API Example

# momentum_strategy.py
from rustybt.api import order_target_percent, symbol, record
from rustybt.utils.run_algo import run_algorithm
import pandas as pd

def initialize(context):
    """Initialize strategy."""
    context.assets = [symbol('AAPL'), symbol('MSFT'), symbol('GOOGL')]
    context.lookback = 20

def handle_data(context, data):
    """Execute on each bar."""
    for asset in context.assets:
        prices = data.history(asset, 'price', context.lookback, '1d')
        momentum = (prices[-1] - prices[0]) / prices[0]

        if momentum > 0.02:
            order_target_percent(asset, 0.33)
        elif momentum < -0.02:
            order_target_percent(asset, 0)

        record(**{asset.symbol: prices[-1], f"{asset.symbol}_momentum": momentum})

if __name__ == "__main__":
    result = run_algorithm(
        initialize=initialize,
        handle_data=handle_data,
        bundle='yfinance-profiling',
        start=pd.Timestamp('2020-01-01'),
        end=pd.Timestamp('2023-12-31'),
        capital_base=100000,
        data_frequency='daily'
    )

    # Access results directly
    print(f"\n{'='*60}")
    print("Backtest Results")
    print(f"{'='*60}")
    print(f"Total Return:    {result['returns'].iloc[-1]:.2%}")
    print(f"Sharpe Ratio:    {result['sharpe']:.2f}")
    print(f"Max Drawdown:    {result['max_drawdown']:.2%}")
    print(f"Final Value:     ${result['portfolio_value'].iloc[-1]:,.2f}")
    print(f"{'='*60}\n")

    # Save results
    result.to_csv('backtest_results.csv')

    # Advanced analysis
    import matplotlib.pyplot as plt
    result['portfolio_value'].plot(title='Portfolio Value Over Time')
    plt.savefig('portfolio_chart.png')

Run with:

python momentum_strategy.py

Advantages

✅ Full IDE debugging support (breakpoints, step-through) ✅ Direct access to results DataFrame ✅ Easy integration with notebooks and scripts ✅ More Pythonic and familiar workflow ✅ Programmatic results handling

Limitations

❌ Requires code modifications ❌ More verbose than CLI


Class-Based vs Function-Based

Function-Based Strategies

Best for: Simple strategies, quick prototypes, learning

def initialize(context):
    context.asset = symbol('AAPL')

def handle_data(context, data):
    price = data.current(context.asset, 'price')
    if price > 150:
        order(context.asset, 100)

# Run with CLI or Python API
run_algorithm(initialize=initialize, handle_data=handle_data, ...)

Advantages: - Minimal boilerplate - Quick to write - Easy to understand

Limitations: - Limited organization for complex logic - No class methods or inheritance - Cannot use Pipeline API

Class-Based Strategies

Best for: Complex strategies, Pipeline API, production code

from rustybt.algorithm import TradingAlgorithm
from rustybt.pipeline import Pipeline

class MomentumStrategy(TradingAlgorithm):
    """Complex strategy using Pipeline API."""

    def initialize(self):
        """Initialize with state and pipeline."""
        self.lookback = 20
        self.threshold = 0.02

        # Attach pipeline for screening
        pipe = self.make_pipeline()
        self.attach_pipeline(pipe, 'momentum_screen')

        # Schedule functions
        self.schedule_function(
            self.rebalance,
            self.date_rules.week_start(),
            self.time_rules.market_open()
        )

    def make_pipeline(self):
        """Create data pipeline."""
        from rustybt.pipeline.factors import Returns
        momentum = Returns(window_length=self.lookback)
        return Pipeline(
            columns={'momentum': momentum},
            screen=momentum.top(50)
        )

    def rebalance(self, context, data):
        """Rebalance portfolio."""
        pipeline_data = self.pipeline_output('momentum_screen')
        target_assets = set(pipeline_data.index)

        # Equal weight allocation
        weight = 1.0 / len(target_assets) if target_assets else 0
        for asset in target_assets:
            self.order_target_percent(asset, weight)

    def handle_data(self, context, data):
        """Monitor and log."""
        self.log.info(f"Portfolio value: ${context.portfolio.portfolio_value:,.2f}")

# Save to momentum_strategy.py and run with CLI
# rustybt run -f momentum_strategy.py -b yfinance-profiling \
#   --start 2020-01-01 --end 2023-12-31 --capital-base 100000

Class-Based Strategies Require CLI

Strategies inheriting from TradingAlgorithm must be run using the CLI (rustybt run -f). The Python API run_algorithm() function does not support class-based strategies.

Advantages: - Organized code structure - Supports Pipeline API - Reusable through inheritance - State encapsulation - Method reuse

Limitations: - More boilerplate - Steeper learning curve


Jupyter Notebook Execution

Interactive Development

Jupyter notebooks provide an interactive environment for strategy development and analysis:

# Cell 1: Imports and setup
from rustybt.api import order_target_percent, symbol
from rustybt.utils.run_algo import run_algorithm
import pandas as pd
import matplotlib.pyplot as plt

# Cell 2: Define strategy
def initialize(context):
    context.asset = symbol('AAPL')

def handle_data(context, data):
    price = data.current(context.asset, 'price')
    ma_20 = data.history(context.asset, 'price', 20, '1d').mean()

    if price > ma_20:
        order_target_percent(context.asset, 1.0)
    else:
        order_target_percent(context.asset, 0)

# Cell 3: Run backtest
result = run_algorithm(
    initialize=initialize,
    handle_data=handle_data,
    bundle='yfinance-profiling',
    start=pd.Timestamp('2020-01-01'),
    end=pd.Timestamp('2023-12-31'),
    capital_base=10000
)

# Cell 4: Analyze results
print(f"Total Return: {result['returns'].iloc[-1]:.2%}")
result['portfolio_value'].plot(figsize=(12, 6), title='Portfolio Value')
plt.show()

# Cell 5: Detailed analysis
result['returns'].hist(bins=50, figsize=(10, 6))
plt.title('Returns Distribution')
plt.show()

# Cell 6: Performance metrics
sharpe = result['sharpe']
max_dd = result['max_drawdown']
print(f"Sharpe Ratio: {sharpe:.2f}")
print(f"Max Drawdown: {max_dd:.2%}")

Advantages

✅ Interactive development and testing ✅ Inline visualization and analysis ✅ Easy parameter exploration ✅ Shareable research notebooks ✅ Documentation with markdown cells

Limitations

❌ Not suitable for production ❌ Version control challenges ❌ Execution order matters ❌ State management issues

Best Practices

  1. Clear cell execution order - Number cells, document dependencies
  2. Restart kernel frequently - Avoid stale state
  3. Extract to .py files - Move production code out of notebooks
  4. Use for exploration only - Not for live trading

Multi-Strategy Portfolios

Overview

Multi-Strategy Portfolios allow you to run multiple independent trading strategies simultaneously, each with isolated capital and positions.

Multi-Asset vs Multi-Strategy

Multi-Asset Portfolio (ONE strategy, MULTIPLE assets):

def handle_data(context, data):
    # Single strategy trading multiple assets
    for asset in context.assets:
        order(asset, 100)

Multi-Strategy Portfolio (MULTIPLE strategies, each with independent capital):

# Strategy 1: Momentum (40% capital)
# Strategy 2: Mean Reversion (35% capital)
# Strategy 3: Trend Following (25% capital)
# Each strategy operates independently with isolated positions

Using PortfolioAllocator with run_algorithm()

Use the PortfolioAllocator class inside your initialize and handle_data functions:

from decimal import Decimal
from rustybt.portfolio.allocator import PortfolioAllocator
from rustybt.utils.run_algo import run_algorithm
import pandas as pd

# Define individual strategies
class MomentumStrategy:
    def __init__(self):
        self.assets = [symbol(s) for s in ['AAPL', 'MSFT', 'GOOGL']]

    def handle_data(self, context, data, ledger):
        # Uses THIS strategy's capital only
        for asset in self.assets:
            # ... momentum logic ...
            order_target_percent(asset, 0.33, ledger=ledger)

class MeanReversionStrategy:
    def __init__(self):
        self.assets = [symbol(s) for s in ['TLT', 'GLD', 'VNQ']]

    def handle_data(self, context, data, ledger):
        # Uses THIS strategy's capital only
        for asset in self.assets:
            # ... mean reversion logic ...
            order_target_percent(asset, 0.33, ledger=ledger)

# Coordinator functions
def initialize(context):
    # Create portfolio allocator
    context.portfolio_allocator = PortfolioAllocator(
        total_capital=Decimal("1000000"),  # $1M
        name="Multi-Strategy Portfolio"
    )

    # Add strategies with capital allocations
    context.momentum = MomentumStrategy()
    context.portfolio_allocator.add_strategy(
        strategy_id="momentum",
        strategy=context.momentum,
        allocation_pct=Decimal("0.60")  # 60% = $600K
    )

    context.mean_rev = MeanReversionStrategy()
    context.portfolio_allocator.add_strategy(
        strategy_id="mean_reversion",
        strategy=context.mean_rev,
        allocation_pct=Decimal("0.40")  # 40% = $400K
    )

def handle_data(context, data):
    # Execute all strategies synchronously
    context.portfolio_allocator.execute_bar(
        timestamp=context.datetime,
        data=data
    )

# Run the multi-strategy portfolio
result = run_algorithm(
    initialize=initialize,
    handle_data=handle_data,
    bundle='yfinance-profiling',
    start=pd.Timestamp('2020-01-01', tz='UTC'),
    end=pd.Timestamp('2023-12-31', tz='UTC'),
    capital_base=1000000
)

Key Features

Strategy Isolation: Each strategy has its own DecimalLedger with isolated cash and positions ✅ Capital Allocation: Allocate percentage of total capital to each strategy ✅ Synchronized Execution: All strategies execute on the same bar (timestamp) ✅ Performance Tracking: Per-strategy and portfolio-level metrics ✅ Dynamic Rebalancing: Adjust allocations based on performance (optional)

When to Use Multi-Strategy

Use multi-strategy portfolios when: - Running multiple uncorrelated strategies for diversification - Managing different risk profiles (conservative, moderate, aggressive) - Testing strategy combinations (momentum + mean reversion + trend) - Building hedge fund-style portfolios with isolated strategies

Don't use multi-strategy when: - You only need to trade multiple assets with one strategy (use simple for loop) - Strategies are highly correlated (no diversification benefit) - You need strategies to share positions (violates isolation principle)

Complete Guide

For comprehensive documentation including: - Allocation algorithms (Fixed, Dynamic, Risk Parity, Kelly, Drawdown-Based) - Performance tracking and analysis - Order aggregation to reduce commissions - Rebalancing strategies - Complete working examples

See: Multi-Strategy Portfolio Guide

Example Notebook: 09_multi_strategy_portfolio.ipynb

API Reference: Portfolio Allocation & Multi-Strategy Management


Choosing the Right Method

Decision Matrix

Use CLI when: - Running existing strategies quickly - Automating backtests in scripts - Deploying to production servers - You don't need result post-processing

Use Python API when: - Developing strategies actively - Need IDE debugging features - Want programmatic result access - Integrating with other Python code

Use Class-based when: - Strategy is complex (>100 lines) - Using Pipeline API - Need state encapsulation - Building reusable strategy templates

Use Function-based when: - Strategy is simple (<100 lines) - Quick prototyping - Learning RustyBT - Don't need Pipeline API

Use Jupyter Notebooks when: - Exploring data and ideas - Creating research reports - Teaching or documentation - Iterative development


Complete Working Examples

Example 1: Simple Moving Average (Function-Based, Python API)

# sma_strategy.py
from rustybt.api import order_target, symbol, record
from rustybt.utils.run_algo import run_algorithm
import pandas as pd

def initialize(context):
    context.asset = symbol('AAPL')
    context.i = 0

def handle_data(context, data):
    context.i += 1
    if context.i < 300:
        return

    short_ma = data.history(context.asset, 'price', 100, '1d').mean()
    long_ma = data.history(context.asset, 'price', 300, '1d').mean()

    if short_ma > long_ma:
        order_target(context.asset, 100)
    elif short_ma < long_ma:
        order_target(context.asset, 0)

    record(short_ma=short_ma, long_ma=long_ma)

if __name__ == "__main__":
    result = run_algorithm(
        initialize=initialize,
        handle_data=handle_data,
        bundle='yfinance-profiling',
        start=pd.Timestamp('2020-01-01'),
        end=pd.Timestamp('2023-12-31'),
        capital_base=10000
    )
    print(f"Return: {result['returns'].iloc[-1]:.2%}")

Example 2: Multi-Asset Portfolio (Class-Based, CLI)

Class-Based Strategies Require CLI

Strategies inheriting from TradingAlgorithm must be run using the CLI (rustybt run -f). The Python API run_algorithm() does not support class-based strategies.

# portfolio_strategy.py
from rustybt.algorithm import TradingAlgorithm
from rustybt.api import order_target_percent, symbol

class EqualWeightPortfolio(TradingAlgorithm):
    def initialize(self):
        self.assets = [
            symbol('AAPL'), symbol('MSFT'),
            symbol('GOOGL'), symbol('AMZN')
        ]
        self.schedule_function(
            self.rebalance,
            self.date_rules.month_start(),
            self.time_rules.market_open()
        )

    def rebalance(self, context, data):
        weight = 1.0 / len(self.assets)
        for asset in self.assets:
            order_target_percent(asset, weight)

Save to file and run with CLI:

rustybt run -f portfolio_strategy.py \
    -b yfinance-profiling \
    --start 2020-01-01 \
    --end 2023-12-31 \
    --capital-base 100000

Troubleshooting

Common Issues

"ModuleNotFoundError: No module named 'rustybt'"

pip install rustybt

"No data for bundle 'yfinance-profiling'"

rustybt ingest -b yfinance-profiling

"NameError: name 'symbol' is not defined"

# Add import
from rustybt.api import symbol

"AttributeError: 'NoneType' object has no attribute 'iloc'"

Check that run_algorithm() completed successfully. Add error handling:

result = run_algorithm(...)
if result is not None:
    print(f"Return: {result['returns'].iloc[-1]:.2%}")
else:
    print("Backtest failed!")

Next Steps

  1. Try the Quick Start - Quick Start Guide
  2. Explore Examples - Examples & Notebooks
  3. Learn Pipeline API - Pipeline API Guide
  4. Deploy to Production - Live Trading Guide

Last Updated: 2025-10-17 Related Documentation: - Quick Start Guide - Order Types API - Portfolio Management API - Pipeline API Guide