Files
strategy-lab/to_explore/pyquantnews/52_AssetClassTrendFollowing.ipynb
David Brazda e3da60c647 daily update
2024-10-21 20:57:56 +02:00

8.5 KiB

No description has been provided for this image

This code implements a momentum-based trading strategy using Zipline, a backtesting library for Python. It calculates the Simple Moving Average (SMA) of selected ETFs over a 10-month period. The strategy rebalances the portfolio at the start of each month, investing in ETFs that are above their SMA. It also sets realistic commission and slippage values. The final performance of the strategy is evaluated using Pyfolio for detailed analysis.

In [ ]:
import warnings
In [ ]:
warnings.filterwarnings("ignore")
In [ ]:
import pandas as pd
import pandas_datareader.data as web
In [ ]:
from zipline import run_algorithm
from zipline.api import (
    attach_pipeline,
    date_rules,
    order_target_percent,
    pipeline_output,
    record,
    schedule_function,
    symbol,
    time_rules,
    get_open_orders,
)
from zipline.finance import commission, slippage
from zipline.pipeline import Pipeline
from zipline.pipeline.factors import SimpleMovingAverage
from zipline.pipeline.data import USEquityPricing
In [ ]:
import pyfolio as pf
In [ ]:
def initialize(context):
    """
    Initialization function for setting up the algorithm.

    Defines the ETFs to be traded, sets the SMA period, and schedules the rebalance function.

    Parameters
    ----------
    context : object
        A context object holding the state of the algorithm.

    Returns
    -------
    None
    """

    context.symbols = [
        symbol("SPY"),
        symbol("EFA"),
        symbol("IEF"),
        symbol("VNQ"),
        symbol("GSG"),
    ]

    # Create an empty dictionary to store Simple Moving Average values for each ETF

    context.sma = {}

    # Define the SMA period as 10 months (approximately 21 trading days per month)

    context.period = 10 * 21

    # Calculate the SMA for each ETF over the defined period

    for asset in context.symbols: 
        context.sma[asset] = SimpleMovingAverage(
            inputs=[USEquityPricing.close],
            window_length=context.period
        )
    
    # Schedule the rebalance function to run at the start of each month, one minute after market opens

    schedule_function(
        func=rebalance,
        date_rule=date_rules.month_start(),
        time_rule=time_rules.market_open(minutes=1),
    )

    # Set commission and slippage to realistic values

    context.set_commission(
        commission.PerShare(cost=0.01, min_trade_cost=1.00)
    )
    context.set_slippage(slippage.VolumeShareSlippage())
In [ ]:
def rebalance(context, data):
    """
    Rebalance function to be run at the start of each month.

    Adjusts the portfolio by closing positions not in the 'longs' list and setting target percentages for 'longs'.

    Parameters
    ----------
    context : object
        A context object holding the state of the algorithm.
    data : object
        A data object providing market data.

    Returns
    -------
    None
    """

    longs = [
        asset
        for asset in context.symbols
        if data.current(asset, "price") > context.sma[asset].mean()
    ]

    # Close positions for assets not in 'longs'

    for asset in context.portfolio.positions:
        if asset not in longs and data.can_trade(asset):
            order_target_percent(asset, 0)

    # Set target portfolio percentage for each asset in 'longs'

    for asset in longs:
        if data.can_trade(asset):
            order_target_percent(asset, 1.0 / len(longs))
In [ ]:
start = pd.Timestamp("2010")
end = pd.Timestamp("2023-06-30")
In [ ]:
sp500 = web.DataReader('SP500', 'fred', start, end).SP500
benchmark_returns = sp500.pct_change()

Run the algorithm with the specified parameters and capture performance

In [ ]:
perf = run_algorithm(
    start=start,
    end=end,
    initialize=initialize,
    capital_base=100000,
    bundle='quandl-eod'
)
In [ ]:
perf

Extract returns, positions, and transactions from the performance DataFrame

In [ ]:
returns, positions, transactions = \
    pf.utils.extract_rets_pos_txn_from_zipline(perf)

Create a full tear sheet to analyze the performance of the strategy

In [ ]:
pf.create_full_tear_sheet(
    returns,
    positions=positions,
    transactions=transactions,
    round_trips=True,
)

PyQuant News is where finance practitioners level up with Python for quant finance, algorithmic trading, and market data analysis. Looking to get started? Check out the fastest growing, top-selling course to get started with Python for quant finance. For educational purposes. Not investment advise. Use at your own risk.