Key Concepts
Duck Typing
Introduction
Duck typing is popular type system in Python. The language documentation defines duck typing as follows:
A programming style which does not look at an object's type to determine if it has the right interface; instead, the method or attribute is simply called or used (“If it looks like a duck and quacks like a duck, it must be a duck.”) By emphasizing interfaces rather than specific types, well-designed code improves its flexibility by allowing polymorphic substitution.
Duck-typing avoids tests using
type()
orisinstance()
. (Note, however, that duck-typing can be complemented with abstract base classes.) Instead, it typically employshasattr()
tests or EAFP programming.
You can use duck-typing to add attributes to all non-primitive types in Python
# Duck-typing enables you to add attributes to non-primitive types in Python. class Duck: pass duck = Duck() duck.quacks = "Quack!" print(duck.quacks)
Custom Security Properties
You can add attributes to the Security
object. For example, you can add an exponential moving average.
# Add an EMA indicator to the SPY Equity object. equity = self.add_equity("SPY") equity.ema = self.ema(equity.symbol, 10, Resolution.DAILY)
This feature is helpful because you can get the Security
object from the securities
object.
# Get the SPY Equity object from the securities object to get the EMA indicator. ema = self.securities["SPY"].ema.current.value
Other Types
You can add properties to all types. However, these properties live in the Python space, so you can't access them without a reference.
The following example demonstrates that you can add an exponential moving average to the symbol
attribute of a Security
object.
# Add an EMA to the SPY security. equity = self.add_equity("SPY") self._symbol = equity.symbol self._symbol.ema = self.ema(self._symbol, 10) ema = self._symbol.ema.current.value ema = self.securities["SPY"].symbol.ema.current.value
Examples
The following examples demonstrate some common practices for duck typing.
Example 1: Store EMA Indicator
The following example implements a trend-following strategy using the EMA indicator for the top 20 liquid equities. It uses duck typing to store the EMA indicator in each
Security object
, so the indicator is discarded together with the
Security
when it is removed from the universe.
class DuckTypingAlgorithm(QCAlgorithm): def initialize(self) -> None: self.set_start_date(2022, 1, 1) self.set_end_date(2022, 6, 1) # Weekly renewal on the universe to allow time to capitalize on the trend. self.universe_settings.schedule.on(self.date_rules.week_start()) # Trade on the top 20 liquid equities since they have higher volume and hence price movements. self._universe = self.add_universe(self.universe.dollar_volume.top(20)) def on_data(self, slice: Slice) -> None: for kvp in self._universe.members: symbol, security = kvp.key, kvp.value # Trade on updated price data. bar = slice.bars.get(symbol) if bar: ema = security.ema.current.value # Switch to long if the current price is above EMA, suggesting an uptrend. if bar.close > ema and not self.portfolio[symbol].is_long: self.set_holdings(symbol, 0.025) elif bar.close < ema and not self.portfolio[symbol].is_short: self.set_holdings(symbol, -0.025) def on_securities_changed(self, changes: SecurityChanges) -> None: for removed in changes.removed_securities: # Liquidate if it is not in the top 50 liquidity stocks anymore. self.liquidate(removed.symbol) # Release resources that update automatic indicators self.deregister_indicator(removed.ema) for added in changes.added_securities: # Use duck typing to create an EMA indicator for trend trading. # Discarded together when the security is removed from the universe, you won't need to handle it. added.ema = self.ema(added.symbol, 60, Resolution.DAILY) # Warm up the EMA indicator to ensure its readiness for immediate usage. self.warm_up_indicator(added.symbol, added.ema, Resolution.DAILY)