I'm noticing a lot of people posting strategies on QuantConnect using the following anti-pattern:
class SomeAlgo(QCAlgorithm):
def initialize(self):
self.entity = self.add_equity("SOME_SYMBOL", Resolution.DAILY).symbol
self.schedule.on(
self.date_rules.every_day(self.entity),
self.time_rules.before_market_close(self.entity, 5),
self.rebalance
)
...
def rebalance(self):
self.set_holdings(self.entity, some_fraction)
The backtest reports strong values, but there's a severe backtest flaw in the above pattern.
Because self.entity is set to Resolution.DAILY, self.set_holdings will fill the price to the last closing bar price. In the above, because we are 5 minutes away from market closing, it will fill the price at yesterday's close price. What does this mean? We can cheat by doing something like:
class FakeAmazingAlgo(QCAlgorithm):
def initialize(self):
self.entity = self.add_equity("SPY", Resolution.DAILY).symbol
# Usually, this is SMA or something derived from minute data, but I'll just
# set it directly to price to prove a point
self.entity_minute = self.add_equity("SPY", Resolution.MINUTE).symbol
self.schedule.on(
self.date_rules.every_day(self.entity),
self.time_rules.before_market_close(self.entity, 5),
self.rebalance
)
...
def rebalance(self):
# Note, this is usually hidden with some indicator like SMA, EMA, etc.
indicator_value = self.securities[self.entity_minute].price
indicator_threshold = self.securities[self.entity].price
if indicator_value > indicator_threshold:
self.set_holdings(self.entity, 1.0)
else:
self.liquidate(self.entity)
In the above, we are literally doing the following: we will buy SPY at yesterday's close price if the price right now is greater than yesterday's close price. Otherwise, liquidate at yesterday's close price.
Too many people are using the above anti-pattern. The correct pattern is either:
- Change the resolution of the entity you are trading to MINUTE
class SomeAlgo(QCAlgorithm):
def initialize(self):
## Change the resolution of entity we are trading to MINUTE so that it's price is updated by minute
self.entity = self.add_equity("SOME_SYMBOL", Resolution.MINUTE).symbol
self.schedule.on(
self.date_rules.every_day(self.entity),
self.time_rules.before_market_close(self.entity, 5),
self.rebalance
)
...
def rebalance(self):
self.set_holdings(self.entity, some_fraction)
OR 2. Use self.market_on_close_order with at least 16 minutes before closing (need to check, but IIRC, it was minimum of 15 minutes before closing to establish a MOC order)
class SomeAlgo(QCAlgorithm):
def initialize(self):
self.entity = self.add_equity("SOME_SYMBOL", Resolution.DAILY).symbol
self.schedule.on(
self.date_rules.every_day(self.entity),
self.time_rules.before_market_close(self.entity, 5),
self.rebalance
)
...
def rebalance(self):
self.market_on_close_order(self.entity, ... )
Here's an example of a flawed algorithm (was scratching my head why this was performing so well in backtest but performance differed in live).
Alexandre Catarino
Hi Ray Juang ,
This flawed pattern is documented at Scheduled Events > Common Errors, and we instruct our AI assistants (e.g., Mia) not to use it. We have considered throwing an exception, but it may be too radical, since Quants may want to prototype with daily and then flip to minute for a final test.
Best regards,
Alex
Jared Broad
Hi Ray - We've started the work to make bars fill on their current close by default with an explicit override to revert to this old behavior. It should be out in a few days. It will break people relying on filling of daily bars with the current instant behavior.
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.
Alexandre Catarino
Hi Ray Juang ,
We have addressed the issue with daily resolution.
QCAlgorithm.MarketOrder now converts these orders so they fill at a real daily open/close instead of the stale previous close:
- market closed (any resolution): MarketOnOpen, as before
- market open, daily-only subscription: MarketOnClose (today's close), or MarketOnOpen (next open) when already within the MarketOnClose submission buffer near the close
Best regards,
Alex
Ray Juang
Alexandre Catarino isn't this just creating new problems ? If QCAlgorithm.MarketOrder now converts these orders so they fill at a real daily open/close instead of the stale previous close, we're potentially leaking future price today. We should be filling the open/close based on the following criteria:
Ray Juang
Hmm.. thinking about it again, I think the solution implemented does solve the problem. Placing an order near end of day or before opening should resolve properly now. Just wanted to confirm, if something placed before market opens, it goes to next open price for backtest ?
Ray Juang
Just went through the top strategies on the leaderboard.. they all look real! This is awesome! Now the top algorithms are real now! Thanks Alexandre Catarino
Alexandre Catarino
Hi Ray Juang,
Please check out the announcement on the change
Closing the Stale-Price Loophole on Daily and Hourly Data
Best regards,
Alex
Ray Juang
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.
To unlock posting to the community forums please complete at least 30% of Boot Camp.
You can continue your Boot Camp training progress from the terminal. We hope to see you in the community soon!