Hello,
I think a working example of this could be very helpful for adoption, especially for users looking to implement daily resolution Swing Trading strategies who need up-to-date OHLC (and Volume) data prior to making a trading decision right before Market Close.
This is common functionality in trading scripting languages like: AmiBroker AFL, NinjaTrader NinjaScript, TradingView PineScript, and TradeStation EasyLanguage.
In these scripting languages it's possible to access the current intraday (prior to market close) values of OHLC data just simply be referencing High[0] or Volume[0], etc and executing the script intraday.
From doing a number of searches on these forums I see that I can get the current equity price by referencing "Price" instead of "Close" however I am still confused on how to get the rest of the current Open, High, Low, and Volume intraday values on a daily resolution prior to market close.
Here is a simple example of the algorithm I am using:
class SwingTrade(QCAlgorithm):
def Initialize(self):
self.SetCash(100000)
self.SetStartDate(2021,4,15)
self.SetEndDate(2021,4,15)
self.symbol = self.AddEquity("SPY", Resolution.Daily).Symbol
self.lookback = 200
# Schedule function 16 minutes before every market close
self.Schedule.On(self.DateRules.EveryDay(self.symbol), \
self.TimeRules.BeforeMarketClose(self.symbol, 16), \
Action(self.EveryMarketClose))
def EveryMarketClose(self):
# get historical data - ends yesterday
self.opens = self.History(self.symbol, self.lookback, Resolution.Daily)["open"]
self.highs = self.History(self.symbol, self.lookback, Resolution.Daily)["high"]
self.lows = self.History(self.symbol, self.lookback, Resolution.Daily)["low"]
self.closes = self.History(self.symbol, self.lookback, Resolution.Daily)["close"]
self.volume = self.History(self.symbol, self.lookback, Resolution.Daily)["volume"]
# now append today's value of the above using data 16 minutes prior to market close
# ?????
Thanks
Brian Sax
I thought maybe a consolidator would allow me to get the current days OHLC and Volume values prior to close, however when I run a backtest I get very unexpected results...
I set StartDate and EndDate to 4/15/21 yet when I run the backtest, my consolidator is being hit on 4/13/21 and 4/14/21 and not on the one day I would expect it to be called (4/15/21)? Further the PreMarketCloseHandler() is reporting a SPY Close of 415.87 on 4/14/21 even though SPY's High didn't reach $414 that day?
Log
PreMarketCloseHandler() called at 2021-04-13 22:46:00, current Close price is 411.45
EveryMarketClose() called.
Spy closed at 415.87 on 4/15/21 at 4:00 PM, current SPY price at 3:43PM is: 411.45
PreMarketCloseHandler() called at 2021-04-14 23:38:00, current Close price is 415.87
Code
from clr import AddReference AddReference("System") AddReference("QuantConnect.Algorithm") AddReference("QuantConnect.Common") from System import * from QuantConnect import * from QuantConnect.Algorithm import * from QuantConnect.Data.Consolidators import * from datetime import timedelta class SwingTrade(QCAlgorithm): def Initialize(self): self.SetCash(100000) self.SetStartDate(2021,4,15) self.SetEndDate(2021,4,15) self.symbol = self.AddEquity("SPY", Resolution.Daily).Symbol self.lookback = 200 self.premarketCloseData = {} # define our 374 minute trade bar consolidator. we can # access the 374 minute bar from the DataConsolidated events preMarketCloseConsolidator = TradeBarConsolidator(timedelta(minutes=373)) # attach our event handler. the event handler is a function that will # be called each time we produce a new consolidated piece of data. preMarketCloseConsolidator.DataConsolidated += self.PreMarketCloseHandler # this call adds our 30 minute consolidator to # the manager to receive updates from the engine self.SubscriptionManager.AddConsolidator("SPY", preMarketCloseConsolidator) # Schedule function 16 minutes before every market close to submit MOC orders self.Schedule.On(self.DateRules.EveryDay(self.symbol), \ self.TimeRules.BeforeMarketClose(self.symbol, 16), \ Action(self.EveryMarketClose)) def EveryMarketClose(self): self.Log("EveryMarketClose() called.") # this is called at the last possible minute to submit MOC orders # get historical data - ends yesterday self.opens = self.History(self.symbol, self.lookback, Resolution.Daily)["open"].values.tolist() self.highs = self.History(self.symbol, self.lookback, Resolution.Daily)["high"].values.tolist() self.lows = self.History(self.symbol, self.lookback, Resolution.Daily)["low"].values.tolist() self.closes = self.History(self.symbol, self.lookback, Resolution.Daily)["close"].values.tolist() self.volumes = self.History(self.symbol, self.lookback, Resolution.Daily)["volume"].values.tolist() # now append today's value of the above using data 16 minutes prior to market close self.opens.append(self.premarketCloseData['open']) self.highs.append(self.premarketCloseData['high']) self.lows.append(self.premarketCloseData['low']) self.closes.append(self.premarketCloseData['close']) self.volumes.append(self.premarketCloseData['volume']) self.Log("Spy closed at 415.87 on 4/15/21 at 4:00 PM, current SPY price at 3:43PM is: {}".format(self.premarketCloseData['close'])) def PreMarketCloseHandler(self, sender, consolidated): self.Log("PreMarketCloseHandler() called at {}, current Close price is {}".format(consolidated.Time,consolidated.Close)) # called 17 minutes prior to market close '''This is our event handler for our 373 minute trade bar defined above in Initialize(). So each time the consolidator produces a new 373 minute bar, this function will be called automatically.''' self.premarketCloseData['open'] = consolidated.Open self.premarketCloseData['high'] = consolidated.High self.premarketCloseData['low'] = consolidated.Low self.premarketCloseData['close'] = consolidated.Close self.premarketCloseData['volume'] = consolidated.Volume
Derek Melchin
Hi Brian,
We have an open GitHub Issue to add this functionality. Subscribe to our progress here.
For the time being, to retrieve a bar that represents the daily data from the open to the current time, we first add a daily consolidator.
self.symbol = self.AddEquity("SPY", Resolution.Minute).Symbol self.consolidator = TradeBarConsolidator(timedelta(1)) self.SubscriptionManager.AddConsolidator(self.symbol, self.consolidator)
We can then access the consolidated bar that's currently being built for the day.
bar = self.consolidator.WorkingBar
See the attached backtest for reference.
Best,
Derek Melchin
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.
Brian Sax
Hi Derek Melchin ,
Thanks for helping me out - I'm excited to get my strategy live.
I have been playing with your code example and while it appears to work on historic days, it is failing when enddate is today's date, for example when running 4-20 to 4-21 earlier this evening:
class DeterminedTanPigeon(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2021, 4, 20) # Set Start Date
self.SetEndDate(2021, 4, 21)
self.SetCash(100000) # Set Strategy Cash
self.symbol = self.AddEquity("SPY", Resolution.Minute).Symbol
self.consolidator = TradeBarConsolidator(timedelta(1))
self.SubscriptionManager.AddConsolidator(self.symbol, self.consolidator)
def OnData(self, data):
if not data.Bars.ContainsKey("SPY"):
return
if data.Time.hour == 15 and data.Time.minute == 59:
bar = self.consolidator.WorkingBar
#self.Debug(str(bar))
self.Log("Bar close {} high {}".format(bar.Close,bar.High))
Output:
2021-04-20 15:59:00 :Bar close 412.455 high 415.21
2021-04-21 15:59:00 :Bar close 411.8 high 411.81
The 4/20 values are correct, but note that it reports a Close of $411.80 on 4/21, whereas SPY actually closed at $416.07.
Brian Sax
Derek Melchin I forward tested your method by deploying it live.
This is what it reported for SPY today at 3:59PM according to the logs:
2021-04-22 19:59:18 Bar close 412.33 high 413.43
Note the Close is accurate but the High is off, the SPY's High today was $416.78.
Derek Melchin
Hi Brian,
We just ran the algorithm again and the logs showed
2021-04-20 15:59:00 : Bar close 412.455 high 415.21 2021-04-21 15:59:00 : Bar close 415.98 high 416.27
The attached notebook shows that on 4/21/21, SPY closed at 416.07. The logs printed 415.98 because that's the latest price we received on SPY at 4/21/21 3:59pm. Lean does not allow look-ahead bias, so it's not possible to know the price of SPY at 4/21/21 4pm when it's only 4/21/21 3:59pm.
See the attached backtest and notebook for reference. Also consider reviewing our documentation on Understanding Time.
In regards to the live deployment of this algorithm, the high price can be off if we start the algorithm after the market open. When was the algorithm deployed? For further assistance, please attach the algorithm that was deployed.
Best,
Derek Melchin
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.
Brian Sax
Derek Melchin -
"... so it's not possible to know the price of SPY at 4/21/21 4pm when it's only 4/21/21 3:59pm."
Yes - agreed, not confused on that, apologize if I was unclear.
What I was trying to say is that there may be a potential bug when the backtest EndDate = the date that you are running the script.
I ran (StartDate 4/20/21 EndDate: 4/21/21) on 4/21/21.
You ran (StartDate 4/20/21 EndDate: 4/21/21) on 4/22/21.
I wonder if you re-ran yours today (4/22/21) with StartDate 4/21/21 and EndDate 4/22/21 if the results would still look correct?
"In regards to the live deployment of this algorithm, the high price can be off if we start the algorithm after the market open. When was the algorithm deployed?"
You are right - I deployed it midday this must have been the issue.
Derek Melchin
Hi Brian,
Thanks for clarifying. Yes, the results would still look correct. However, the data for 4/22/21 wouldn't be available for backtesting until after the market close on 4/22/21.
Best,
Derek Melchin
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.
Brian Sax
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!