Hi,
For a multiple symbol QCAlgorithm strategy that is running on consolidated bars, what is the correct way of doing it?
In the SymbolData object way:
class MultipleTickerSingleTechnicalIndicatorWithResampling(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2022, 2, 1) # Set Start Date
self.SetEndDate(2022, 3, 31) # Set End Date
self.SetCash(100000) # Set Strategy Cash
## PARAMETERS
self.DebugMode = True
self.ema_fast = int(self.GetParameter("ema_fast"))
self.ema_slow = int(self.GetParameter("ema_slow"))
self.consolidate_bar_count = int(self.GetParameter("consolidate_bar_count"))
## UNIVERSE
self.tickers = ['ADAUSDT','ALGOUSDT','ATOMUSDT','AVAXUSDT','AXSUSDT','BCHUSDT','BTCUSDT','DOGEUSDT','DOTUSDT','ETHUSDT']
## DATA LOADING
self.symbol_data = {}
for ticker in self.tickers:
security = self.AddData(type=BinanceTradeBarData, ticker=ticker, resolution=Resolution.Minute)
self.symbol_data[ticker] = SymbolData(
algorithm = self,
symbol = security.Symbol,
ema_fast = self.ema_fast,
ema_slow = self.ema_slow,
consolidate_bar_count = self.consolidate_bar_count
)
def OnOrderEvent(self, orderEvent):
if orderEvent.Status == OrderStatus.Filled:
self.Debug(f"Order: {orderEvent.Symbol.Value} | {orderEvent.FillQuantity} | {orderEvent.UtcTime}")
def OnData(self, data: PythonSlice) -> None:
for ticker in self.tickers:
symbol_data_single = self.symbol_data[ticker]
symbol_data_single.consolidator.Update(data[ticker])
def OnDataConsolidated(self, sender, consolidated_bar:TradeBar):
self.Debug(f"Consolidated data: {consolidated_bar.Time}-{consolidated_bar.EndTime} {consolidated_bar.ToString()}")
# strategy logic and order creation with .Liquidate() and .SetHoldings() here
class SymbolData(object):
def __init__(self, algorithm:QCAlgorithm, symbol, ema_fast:int=50, ema_slow:int=200, consolidate_bar_count:int = 5):
self.symbol = symbol
# indicators
self.fast = ExponentialMovingAverage(ema_fast)
self.slow = ExponentialMovingAverage(ema_slow)
# consolidator
self.consolidator = TradeBarConsolidator(timedelta(minutes=consolidate_bar_count))
self.consolidator.DataConsolidated += algorithm.OnDataConsolidated
algorithm.SubscriptionManager.AddConsolidator(self.symbol, self.consolidator)
# update indicators with consolidated_bar
algorithm.RegisterIndicator(self.symbol, self.fast, self.consolidator)
algorithm.RegisterIndicator(self.symbol, self.fast, self.consolidator)
self.signal = 0
def update(self, time, value):
fast_ema_ready = self.fast.Update(time, value)
slow_ema_ready = self.slow.Update(time, value)
if fast_ema_ready and slow_ema_ready:
fast = self.fast.Current.Value
slow = self.slow.Current.Value
self.signal = (fast - slow) / ((fast + slow) / 2.0)
or doing the manual update within OnDataConsolidated (the consolidated bar handler):
class MultipleTickerSingleTechnicalIndicatorWithResampling(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2022, 2, 1) # Set Start Date
self.SetEndDate(2022, 3, 31) # Set End Date
self.SetCash(100000) # Set Strategy Cash
## PARAMETERS
self.DebugMode = True
self.ema_fast = int(self.GetParameter("ema_fast"))
self.ema_slow = int(self.GetParameter("ema_slow"))
self.consolidate_bar_count = int(self.GetParameter("consolidate_bar_count"))
## UNIVERSE
self.tickers = ['ADAUSDT','ALGOUSDT','ATOMUSDT','AVAXUSDT','AXSUSDT','BCHUSDT','BTCUSDT','DOGEUSDT','DOTUSDT','ETHUSDT']
## DATA LOADING
self.symbol_data = {}
for ticker in self.tickers:
security = self.AddData(type=BinanceTradeBarData, ticker=ticker, resolution=Resolution.Minute)
self.symbol_data[ticker] = SymbolData(
algorithm = self,
symbol = security.Symbol,
ema_fast = self.ema_fast,
ema_slow = self.ema_slow,
consolidate_bar_count = self.consolidate_bar_count
)
def OnOrderEvent(self, orderEvent):
if orderEvent.Status == OrderStatus.Filled:
self.Debug(f"Order: {orderEvent.Symbol.Value} | {orderEvent.FillQuantity} | {orderEvent.UtcTime}")
def OnData(self, data: PythonSlice) -> None:
for ticker in self.tickers:
symbol_data_single = self.symbol_data[ticker]
symbol_data_single.update(data[ticker].EndTime,data[ticker].Price)
symbol_data_single.consolidator.Update(data[ticker])
def OnDataConsolidated(self, sender, consolidated_bar:TradeBar):
self.Debug(f"Consolidated data: {consolidated_bar.Time}-{consolidated_bar.EndTime} {consolidated_bar.ToString()}")
# update indicators with consolidated_bar
symbol_data_single = self.symbol_data[consolidated_bar.Symbol.Value]
symbol_data_single.update(consolidated_bar.EndTime,consolidated_bar.Price)
# strategy logic and order creation with .Liquidate() and .SetHoldings() here
class SymbolData(object):
def __init__(self, algorithm:QCAlgorithm, symbol, ema_fast:int=50, ema_slow:int=200, consolidate_bar_count:int = 5):
self.symbol = symbol
# indicators
self.fast = ExponentialMovingAverage(ema_fast)
self.slow = ExponentialMovingAverage(ema_slow)
# consolidator
self.consolidator = TradeBarConsolidator(timedelta(minutes=consolidate_bar_count))
self.consolidator.DataConsolidated += algorithm.OnDataConsolidated
algorithm.SubscriptionManager.AddConsolidator(self.symbol, self.consolidator)
self.signal = 0
def update(self, time, value):
fast_ema_ready = self.fast.Update(time, value)
slow_ema_ready = self.slow.Update(time, value)
if fast_ema_ready and slow_ema_ready:
fast = self.fast.Current.Value
slow = self.slow.Current.Value
self.signal = (fast - slow) / ((fast + slow) / 2.0)
Any help is appreciated.
Nottakumasato
And since consolidated bar handler is the same for each symbol, do I have to check whether all symbols' data has been updated to the same time?
Vladimir
Nottakumasato
If I were asked "what do I prefer to use to add indicators to consolidated bars?".
The subject of my preference would be dictionaries.
If you are satisfied with my answer, please accept it and don't forget to like it.
Fred Painchaud
Hi Notta,
My 2 cents…
With just a slow and fast EMA per symbol, it does not matter much but the “SymbolData” way is generally recommended so for each symbol, in one object, you have encapsulated all your data and indicators. It is generally sound.
Now, in your first algo, in OnData, you do not check to see if there's data or not for each of your symbols before you do “symbol_data_single.consolidator.Update(data[ticker])”. You're gonna hit moments when you will Update with None.
In SymbolData.__init__, you also register self.fast twice with:
algorithm.RegisterIndicator(self.symbol, self.fast, self.consolidator)algorithm.RegisterIndicator(self.symbol, self.fast, self.consolidator)
Finally, you never call your SymbolData.update method. But you do register your indicators so you should not update them manually on top of it.
I just glanced over it but your update logic in the second one might be ok. You still don't do proper sanity checks in OnData however.
Fred
Nottakumasato
Vladimir thank you for the suggestion but I think I like the SymbolData way (seems more organized to me)
Fred Painchaud thank you for the comments too. Would something like below work for checking data=None per ticker?
Also as posted above, should I check whether all symbols' data has been updated to the same time?
Fred Painchaud
Hi Notta,
data[ticker] will exception if ticker is not in it.
Now that I spot it:
Also, the best would be not to map ticker strings to symboldata objects but symbols.
Hence the name SymbolData 😊. Tickers are to be used only at the beginning to sub to an asset, for instance. Then, it's recommended to work with symbols. When you use tickers, symbols are searched for anyway in the background in a cache.
Then you would iterate on your dictionary:
or if you also need values:
Re your other post, by “symbol_datas” I guess you mean “self.symbol_data” ? And “last_update_datetime” would be added to SymbolData.
That said, you want to do it like at the end of OnDataConsolidated? Bu that method will be called once per symbol subbed. You'd need to call that sanity check after all are updated. I would not do it. But well, later on, keep in mind your indie can become out of sync anytime. Say there is an issue with the data feed for a particular asset and thus you stop receiving new data for that asset alone…. So the sanity check in question is a good idea - I just implement all my sanity checks centrally, in a class of invariants I check anytime it makes sense. The reactions to detected errors are also centralized in that class. Anyway - that's all a matter of tastes and engineering.
Fred
Nottakumasato
Fred Painchaud added your suggestions:
So `symbol_datas` are just the SymbolData instances for each symbol in universe.
Exactly like you said, the `last_update_datetime` is an attribute of SymbolData and updated when indicators are updated via `update_indicators()` in `OnDataConsolidated()`.
One question though. Could you elaborate on `I just implement all my sanity checks centrally, in a class of invariants I check anytime it makes sense. The reactions to detected errors are also centralized in that class.`? If there is a small example of an algorithm you implemented this way, I would love to take a look as an example for my future algorithms!
Fred Painchaud
Hey,
What I meant is that I implement my sanity checks in a SanityChecks class I define.
That's part of the library I've been building but sharing it won't help as it all works on Numpy ndarrays, numba, ray, etc. I've re-implemented almost everything in the “middle” of LEAN. All “algo business logic”, more or less. I'm using the “data feeder” (History, Universes, manual subs) and the Orders/Broker proxies. So I'm like compatible (I can use Live nodes) but I don't use much of the class hierarchy in LEAN for algo implementation. I'd need to document it to a great extent if shared as it is documented simply for me not to get lost, so no time now. Sorry.
Fred
Vladimir
Nottakumasato,
--> I like the SymbolData way (seems more organized to me)
When you're done developing with SymbolData, please don't forget
to post a backtest to compare performance and readability.
Nottakumasato
Fred Painchaud I see, no worries at all, still a very good idea to have a class to just import your checks from.
Vladimir yep will do. Have to fix a timeout issue when using remote files but will definitely post an example of a market neutral strategy that I have been working on.
Nottakumasato
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!