Back to Community
Guidelines for minimizing impact of trading costs

This is a backtest of an extremely simple strategy which I'll also try to attach to this post. It's long only, just holding a bigger position when SPY goes above a moving average, and going to cash when it goes below. It has positive (not necessarily good, just positive) returns. It also has a stop loss.

As soon as I turn on any commission or slippage, though, it gets absolutely destroyed and the only thing that remains consistent is losing money :)

I'm tracking the amount of trades, and it's 35000 in SPY trades in about 20 years, or about 7 trades per day. It trades a lot some days, even 100 times; other days it trades two times.

What's surprising to me is that I wouldn't think this amount of trading would make a profitable algorithm just consistently lose money. I thought this wouldn't be considered that many trades. I bet there are retail traders who have made a similar amount of trades!

So where's the big problem I'm overlooking? Is there some tool to analyze intraday algorithms, quantify the impacts of slippage and commissions and then optimize the algorithm to reduce them?

Clone Algorithm
1
Loading...
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
import quantopian.algorithm as algo
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.filters import Q500US

SPY = symbol('SPY')
LEVERAGE = 0.5
STOP_LOSS = 0.95
TRESHOLD = 0.99750
PERIOD = 20
MAX_TRADES_PER_DAY = 9999
def initialize(context):
    set_commission(commission.NoCommission())
    set_slippage(slippage.NoSlippage())
    
    schedule_function(day_begin, date_rules.every_day(), time_rules.market_open())
    schedule_function(day_end, date_rules.every_day(), time_rules.market_close(minutes = 15))
    
    context.prices = {}
    context.direction = None
    context.stop_trading = True
    context.total_trades = 0
    
    #algo.attach_pipeline(make_pipeline(), 'pipe')

def make_pipeline():
    return Pipeline(
        columns = {'dummy': USEquityPricing.close.latest},
        screen = Q500US()
    )
    
def day_begin(context, data):
    context.stop_trading = False
#    context.securities = algo.pipeline_output('pipe').index
    context.securities = [SPY]
    context.data = {x: {"prices": [], "direction": None, "basis": 0} for x in context.securities}
    context.n_trades = 0
    
def day_end(context, data):
    for sec in context.securities:
        order_target(sec, LEVERAGE)
        context.data[sec]["direction"] = None
        context.data[sec]["basis"] = 0
    
    context.stop_trading = True
    context.total_trades += context.n_trades
    record(n_trades = context.total_trades)
        
def handle_data(context, data):
    if context.stop_trading:
        return
        
    for sec in context.securities:
        if context.n_trades > MAX_TRADES_PER_DAY:
            order_target(sec, LEVERAGE)
        
        price = data.current(sec, "price")
        context.data[sec]["prices"].append(price)
        if get_open_orders().get(sec):
            continue

        if len(context.data[sec]["prices"]) < PERIOD:
            continue

        avg = sum(context.data[sec]["prices"][-PERIOD:]) / PERIOD

        if price / avg < TRESHOLD and context.data[sec]["direction"] != "short":
            context.data[sec]["basis"] = price
            context.direction = "short"
            order_target_percent(sec, 0)
        elif avg / price < TRESHOLD and context.data[sec]["direction"] != "long":
            context.data[sec]["basis"] = price
            context.data[sec]["direction"] = "long"
            order_target_percent(sec, LEVERAGE * 2)

        elif context.data[sec]["direction"] == "short" and context.data[sec]["basis"] / price < STOP_LOSS:
            context.data[sec]["direction"] = None
            order_target_percent(sec, LEVERAGE)
        elif context.data[sec]["direction"] == "long" and price / context.data[sec]["basis"] < STOP_LOSS:
            context.data[sec]["direction"] = None
            order_target_percent(sec, LEVERAGE)
        else:
            context.n_trades -= 1
        context.n_trades += 1
There was a runtime error.
4 responses

@Eugenio,

This may help

    record(leverage = context.account.leverage, round_trips = context.round_trips)  

146 round_trips.

The lesson here being just to reduce the amount of trades? I really thought that amount of trading wouldn't be too much. Maybe it's also a matter of the alpha being too weak for the amount of trades?

@ Eugenio,
in your hypothetical back-test (does not include the cost of slippage and commission)
alpha is 0.03, beta 0.10.

My back test takes into account slippage and commission costs,
alpha is 0.09, beta 0.19.

What alpha are you looking for?

My hypothetical backtest is not something I'd actually want to trade live on my account. I'm not saying it's a good algorithm, or sharing it because it's interesting.

It is clear that your algorithm is better, but my algorithm was just an example to ask about how to deal with slippage and commission destroying the alpha of an algorithm. I think what I'm looking for is mostly a general explanation or some practical rules of thumb about the amount of trades or something along those lines