Back

Impossible Backtest Results

I've created my first strategy, and while backtesting it I've noticed that I get impossible results. For example, my strategy loses thousands of dollars each and every day, such that my equity curve goes straight down. If I do nothing to the algo but swap the buy and sell signals, it generates the exact same number of trades but loses almost the same amount of money in the same way.

This is obviously impossible behavior. What might be wrong with my code that would generate this issue?

Thanks!

Update Backtest







0

The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by QuantConnect. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. QuantConnect makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances. All investments involve risk, including loss of principal. You should consult with an investment professional before making any investment decisions.


And I'll add that I am losing far more than just the fees. For example, it lost $16k in a week, and spent $3k in fees. Switching the buy and sell orders yielded the same fees and a $20k loss.

0

Hard to tell what's going on without looking at the code or a description of the trading logic, but you could try adding some Debug/Log statements to see what's going on. 

Unless the algo is a simple buy-and-hold, switching buy/sell logic does not necessarily mean the equity curve flips symmetrically. Having the same fees makes sense since the number of orders doesn't change (and borrowing costs for shorting aren't modeled in). Looking at the win/loss rate, average win/loss magnitudes, etc could be helpful (you can find this in the Reports tab). As an example, if market movements during trades aren't large enough to cross the spread switching buy/sell logic would still result in losses.

0

First off, thank you so much for the reply!

It is not due to the price movement not making up for the spread. I even used limit orders in order to check against this.

I should also say that it is a strategy that I already know works by backtesting it in another program. I've charted it out and made sure the orders are executing correctly, and I have verified through the orders that I am actually turning a profit. I've even set it up while debugging to tell me the profit of each trade and calculate the profit at the end of each day, and made a significant profit each day. For some reason, though, the built in profit calculator is not functioning correctly with it.

My partner and I are kind of baffled at this point. We're new to the api and site so it's probably something silly. Anyone have any ideas?

Thanks again!

0

That is indeed quite strange if the manual calculations work. Again can't really be of much more help without any details (or a simple example backtest without revealing too much of the strategy), but if you suspect it's a bug you could open a ticket with QC Support and have them look at the code confidentially.

I'm sure you've already considered this, but are trades closed every day in the calculations? If not, perhaps due to after-market movements?

 

0

Here is version of the code for you to take a look at. I've only removed the details of the indicators used and their related signals, and plot-specific code that was just unnecessary to include. Thank you for any help you can give!

from clr import AddReference
AddReference("System")
AddReference("QuantConnect.Algorithm")
AddReference("QuantConnect.Indicators")
AddReference("QuantConnect.Common")

from System import *
from QuantConnect import *
from QuantConnect.Data import *
from QuantConnect.Data.Market import *
from QuantConnect.Data.Consolidators import *
from QuantConnect.Algorithm import *
from QuantConnect.Indicators import *
from QuantConnect.Securities import *
from QuantConnect.Orders import *
from datetime import datetime
from System.Drawing import Color

import decimal as d
import numpy as np

class Strat(QCAlgorithm):

def Initialize(self):

# configuration parameters (configurable inputs into the algorithm)
MINUTES_AFTER_OPEN = int(self.GetParameter("MarketOpenDelay")) or 20
MINUTES_BEFORE_CLOSE = int(self.GetParameter("SellMinutesBeforeClose")) or 10
SYMBOL = str(self.GetParameter("Symbol"))

self.ORDER_MAP = ["Market", "Limit", "StopMarket", "StopLimit", "MarketOnOpen", "MarketOnClose", "OptionExercise"]

# initialization
self.SetStartDate(2020, 7, 6)
self.SetEndDate(2020, 7, 10)
self.SetCash(100000)
self.stock = self.AddEquity(SYMBOL, Resolution.Second)
self.SetBrokerageModel(BrokerageName.InteractiveBrokersBrokerage, AccountType.Margin)
self.Schedule.On(self.DateRules.EveryDay(self.stock.Symbol), self.TimeRules.AfterMarketOpen(self.stock.Symbol, MINUTES_AFTER_OPEN), self.OnMarketOpen)
self.Schedule.On(self.DateRules.EveryDay(self.stock.Symbol), self.TimeRules.BeforeMarketClose(self.stock.Symbol, MINUTES_BEFORE_CLOSE), self.OnMarketClose)
self.tradeFlag = False

self.Consolidate(SYMBOL, Resolution.Minute, self.OnStockBarConsolidated)

self.lastPrice = 0.0
self.pl = 0.0

self.SetWarmup(20, Resolution.Minute)

def OnMarketOpen(self):
self.tradeFlag = True

def OnMarketClose(self):
if self.stock.Invested:
self.Liquidate(self.stock.Symbol, "EOD Liquidate")

self.tradeFlag = False
self.Debug("Profit/Loss as of " + str(self.Time) + ": " + str(self.pl) + " | Portfolio Value: " + str(self.Portfolio.TotalPortfolioValue))


self.prevCandle = {
"Open": consolidated.Open,
"High": consolidated.High,
"Low": consolidated.Low,
"Close": consolidated.Close
}

self.Plot('Stock Plot', 'PriceOpen', consolidated.Open)
self.Plot('Stock Plot', 'PriceHigh', consolidated.High)
self.Plot('Stock Plot', 'PriceLow', consolidated.Low)
self.Plot('Stock Plot', 'PriceClose', consolidated.Close)



def OnData(self, data):
if self.IsWarmingUp or not (self.tradeFlag and classified):
return

baropen = float(data[self.stock.Symbol].Open)
barhigh = float(data[self.stock.Symbol].High)
barlow = float(data[self.stock.Symbol].Low)
barclose = float(data[self.stock.Symbol].Close)

if self.stock.Invested:
if self.Portfolio[self.stock.Symbol].IsShort and classified:
self.Liquidate()
if self.Portfolio[self.stock.Symbol].IsLong and classified:
self.Liquidate()
else:

if classified:
self.MarketOrder(self.stock.Symbol, 300, False,"Buy Long")
self.Plot('Stock Plot', 'Buy', barclose)

if classified:
self.MarketOrder(self.stock.Symbol, -300, False,"Sell Short")
self.Plot('Stock Plot', 'Sell', barclose)

def OnOrderEvent(self, OrderEvent):
if OrderEvent.FillQuantity == 0:
return


self.lastPrice = float(OrderEvent.FillPrice)

order = self.Transactions.GetOrderById(OrderEvent.OrderId)
orderType = order.Type
self.pl += order.Value

if "liquid" in (str(order.Tag)).lower():
self.Plot('Stock Plot', 'Liquidate', OrderEvent.FillPrice)

 

0

Couple of potential issues that jump out and you may want to explore:

  • classified is not defined in the example, but in your actual algo when it is not invested and classified is True:
else:
if classified:
self.MarketOrder(self.stock.Symbol, 300, False,"Buy Long")
self.Plot('Stock Plot', 'Buy', barclose)
if classified:
self.MarketOrder(self.stock.Symbol, -300, False,"Sell Short")
self.Plot('Stock Plot', 'Sell', barclose)

Both of these will hit - i.e. the algorithm would Long 300 units, wait for it to fill (default 5 seconds since you have the asynchronous == False flag), then Short 300 units. Since you are using secondly resolution data (meaning OnData is supposed to be called every second), I have no idea what would happen but seems a bit messy. Did you mean to define two separate conditions, i.e. classifiedLong and classifiedShort.?

  • The if statement right above this seems problematic too:
if self.stock.Invested:
if self.Portfolio[self.stock.Symbol].IsShort and classified:
self.Liquidate()
if self.Portfolio[self.stock.Symbol].IsLong and classified:
self.Liquidate()

When classified is True, and the previous order went through (ignoring the 5 second fill time/OnData issue), with the next second you would immediately liquidate the symbol. Then the next second, the cycle repeats - spreads would definitely be a big factor in this case even with Limit Orders.

Possibly, this sort of high frequency trading with weird interactions between fill times and new orders being placed could be causing the issue. Most  other backtesters I've used don't model in fill times (among many other things), so maybe that is why the backtest results here are inconsistent with the other one you used?

0

Thanks again for the reply!

We will take a hard look at that today, but I don't think that's the issue. The "classified" sections are parts of the code I removed to keep the key triggers private, but they are mutually exclusive for the short and long positions so it prevents the behavior you're explaining. As you mentioned, there is a "classifiedlong" and "classifiedshort" to put it simply, and they both can't be true anywhere near the same time, much like the price can't be both below and above a moving average at the same time. Additionally, when looking at the plot of the stock's chart or while debugging it seems clear buy/liquidate/sell short orders are all separated by between 20 seconds to 20 minutes.

Confusing issue, right?

0

Oh I see - thought classified was some boolean flag. That is strange then, if the logic is identical to that of another backtester. Perhaps try turning off a bunch of reality modeling components (i.e. 0 fees, 0 fill times, etc) and see if that helps narrow down what might be happening.

0

Hi Kevin,

It is difficult to assist with debugging this without seeing the full source code.

If you're not comfortable sharing the strategy publicly, I'd recommend submitting a support ticket to have this looked at privately. This short video explains the process of doing so. Note that you'd need to upgrade from a free account to submit a support ticket. Refer to our documentation for information on the packages we have available.

We are committed to protecting your IP. Our Terms & Conditions and Privacy Policy speak about this in great length.

Best,
Derek Melchin

0

The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by QuantConnect. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. QuantConnect makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances. All investments involve risk, including loss of principal. You should consult with an investment professional before making any investment decisions.


Thank you for the reply, Derek. We will likely go that route!

0

Update Backtest





0

The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by QuantConnect. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. QuantConnect makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances. All investments involve risk, including loss of principal. You should consult with an investment professional before making any investment decisions.


Loading...

This discussion is closed