Trailing Stop Code Examples

Does QuantRocket have any Trailing Stop Code examples to be used in Backtests? I have not been able to find any examples. I've attempted to write them on my own using the "Rebalance" and "Exec_Trades" but have hit a wall. Thanks.

I don’t have example code but maybe check the Quantopian Forum archive to see if there’s anything there: https://quantopian-archive.netlify.app/

High-level, you'll want to track the highwater mark of your positions and compare that to their current price to decide whether to liquidate.

1 Like

Brian, in an effort to try something simple with a Stop Loss I came up with the below momentum strategy but I'm receiving an error (listed below) that indicates it may be a Zipline package issue.

import zipline.api as algo
import numpy as np
from zipline.finance import commission, slippage
from zipline.pipeline import Pipeline
from zipline.pipeline.data import EquityPricing
from zipline.pipeline.factors import AverageDollarVolume, Returns
from zipline.pipeline.factors import CustomFactor
from codeload.pipeline_tutorial.tradable_stocks import TradableStocksUS

from zipline.api import (
    attach_pipeline,
    date_rules,
    time_rules,
    get_datetime,
    schedule_function,
    pipeline_output,
    record,
    get_open_orders,
    order_target_percent,
    set_commission,
    set_slippage,
)
        
BUNDLE = "usstock-1min"
DATA_FREQUENCY = "daily"

N_LONGS = 50
    
class Advanced_Momentum(CustomFactor):
        """ Momentum factor """
        inputs = [EquityPricing.close,
                  Returns(window_length=252)]
        window_length = 252

        def compute(self, today, assets, out, prices, returns):
            out[:] = ((prices[-21] - prices[-252])/prices[-252] -
                      (prices[-1] - prices[-21])/prices[-21]) / np.nanstd(returns, axis=0)


def make_pipeline():
    price = EquityPricing.close.latest
    universe_price = price.all_present(252)
    universe = TradableStocksUS() & universe_price
    price_momentum = Advanced_Momentum(mask=universe)
    return Pipeline(
        columns={
            "longs": price_momentum.top(N_LONGS),
            "ranking": price_momentum.rank(ascending=False),
        },
        screen=universe
    )
    
def initialize(context):
    algo.set_benchmark(algo.symbol("SPY"))
    algo.attach_pipeline(make_pipeline(), "pipe")
    algo.schedule_function(
        rebalance,
        algo.date_rules.month_start(),
        algo.time_rules.market_open(),
    )

    algo.set_commission(
        us_equities=commission.PerShare(
            cost=0.005,
            min_trade_cost=2.0,
        )
    )
    algo.set_slippage(
        us_equities=slippage.VolumeShareSlippage(
            volume_limit=0.0025, 
            price_impact=0.01,
        )
    )
    
def before_trading_start(context, data):
    context.pipe = algo.pipeline_output("pipe")
    record(pipe=context.pipe.ranking)
    assets = context.pipe.index
    record(prices=data.current(assets, "price"))

def rebalance(context, data):
    pipe = context.pipe
    assets = pipe.index
    longs = assets[pipe.longs]
    divest = context.portfolio.positions.keys() - longs

    # Print some portfolio details.
    print(
        f"{get_datetime().date()} | Longs {len(longs)} | {context.portfolio.portfolio_value}"
    )

    # Execute the trades with equal weight
    exec_trades(data, assets=divest, context=context, target_percent=0)
    exec_trades(data, assets=longs, context=context, target_percent=2 / N_LONGS)
    
def exec_trades(data, assets, context, target_percent):
    # Loop through every asset...
    for asset in assets:
        # Calculate the stop-loss price (ten percent below purchase price)
        stop_loss_price = context.portfolio.positions[asset].cost_basis * 0.9
        current_price = data.current(asset, 'price')
        
        # If the current price falls below the stop-loss price, sell the asset
        if current_price <= stop_loss_price:
            target_percent = 0
        
        # ...if the asset is tradeable and there are no open orders...
        if data.can_trade(asset) and not get_open_orders(asset):
            # ...execute the order against the target percent
            order_target_percent(asset, target_percent)

from quantrocket.zipline import backtest
backtest("price_momentum_long_only_withstop_strategy",
         progress="M", # Use for long running test. 'D'= daily, 'W'=weeky, 'M'=monthly, 'Q'=quarterly,'A'=annually
         start_date="2017-01-01", end_date="2018-01-10",
         filepath_or_buffer="Price_Momentum_Long_Only_withstop_results.csv") ```

When I run this I receive the following error:

HTTPError Traceback (most recent call last) Cell In[18], line 2 1 from quantrocket.zipline import backtest ----> 2 backtest("price_momentum_long_only_withstop_strategy", 3 progress="M", # Use for long running test. 'D'= daily, 'W'=weeky, 'M'=monthly, 'Q'=quarterly,'A'=annually 4 start_date="2017-01-01", end_date="2018-01-10", 5 filepath_or_buffer="Price_Momentum_Long_Only_withstop_results.csv") File [/opt/conda/lib/python3.11/site-packages/quantrocket/zipline.py:863](http://localhost:1969/opt/conda/lib/python3.11/site-packages/quantrocket/zipline.py#line=862), in backtest(strategy, data_frequency, capital_base, bundle, start_date, end_date, progress, params, filepath_or_buffer) 859 _params["progress"] = progress 861 response = houston.post("[/zipline/backtests/](http://localhost:1969/zipline/backtests/){0}".format(strategy), params=_params, timeout=60*60*96) --> 863 houston.raise_for_status_with_json(response) 865 filepath_or_buffer = filepath_or_buffer or sys.stdout 866 write_response_to_filepath_or_buffer(filepath_or_buffer, response) File [/opt/conda/lib/python3.11/site-packages/quantrocket/houston.py:225](http://localhost:1969/opt/conda/lib/python3.11/site-packages/quantrocket/houston.py#line=224), in Houston.raise_for_status_with_json(response) 223 e.json_response = {} 224 e.args = e.args + ("please check the logs for more details",) --> 225 raise e File [/opt/conda/lib/python3.11/site-packages/quantrocket/houston.py:217](http://localhost:1969/opt/conda/lib/python3.11/site-packages/quantrocket/houston.py#line=216), in Houston.raise_for_status_with_json(response) 212 """ 213 Raises 400[/500](http://localhost:1969/500) error codes, attaching a json response to the 214 exception, if possible. 215 """ 216 try: --> 217 response.raise_for_status() 218 except requests.exceptions.HTTPError as e: 219 try: File [/opt/conda/lib/python3.11/site-packages/requests/models.py:1021](http://localhost:1969/opt/conda/lib/python3.11/site-packages/requests/models.py#line=1020), in Response.raise_for_status(self) 1016 http_error_msg = ( 1017 f"{self.status_code} Server Error: {reason} for url: {self.url}" 1018 ) 1020 if http_error_msg: -> 1021 raise HTTPError(http_error_msg, response=self) HTTPError: ('400 Client Error: BAD REQUEST for url: http://houston/zipline/backtests/price_momentum_long_only_withstop_strategy?start_date=2017-01-01&end_date=2018-01-10&progress=M', {'status': 'error', 'msg': "could not compile /codeload/zipline/price_momentum_long_only_withstop_strategy.py: cannot import name 'StopLimitOrder' from 'zipline.api' ([/opt/conda/lib/python3.11/site-packages/zipline/api.py](http://localhost:1969/opt/conda/lib/python3.11/site-packages/zipline/api.py)) (see detailed logs for full traceback)"})

Any guidance would be great. Thanks.

This error suggests that somewhere you are trying to use algo.StopLimitOrder or from zipline.api import StopLimitOrder which is not where StopLimitOrder is located. (It should be from zipline.finance.execution import StopLimitOrder). The incorrect import is not in the code you pasted but it must be in the price_momentum_long_only_withstop_strategy.py file you are backtesting.

That fixed it. Thank you very much!