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't use duck-typing to add members to regular C# types.
You can use duck-typing to add attributes to all non-primitive types in Python
// Duck-typing doesn't work on regular C# types. class Duck { } var duck = new Duck(); duck.quacks = "Quack!" // This yields a compilation error. dynamic duck = new Duck(); duck.quacks = "Quack!" // This yields a runtime error.
# Duck-typing enables you to add attributes to non-primitive types in Python. class Duck: pass duck = Duck() duck.quacks = "Quack!" print(duck.quacks)
LEAN allows duck-typing in the Security
type.
Custom Security Properties
You can add propertiesattributes to the Security
object. For example, you can add an exponential moving average.
// Cast the Equity to a dynamic object to add an EMA indicator to it. dynamic equity = AddEquity("SPY"); equity.ema = EMA(equity.Symbol, 10, Resolution.Daily);
# 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
securities
object.
// Get the SPY Equity object from Securities to get the EMA indicator. var ema = (decimal)(Securities["SPY"] as dynamic).ema.Current.Value;
# Get the SPY Equity object from the securities object to get the EMA indicator. ema = self.securities["SPY"].ema.current.value
To avoid casting to and from the dynamic
type, you can use the Get<T>
method to access the dynamic member and return the object with its type.
// Get the SPY Equity object from Securities to get the EMA indicator and its current value var ema = Securities["SPY"].Get<ExponentialMovingAverage>("ema"); // The type of ema is ExponentialMovingAverage var emaValue = ema.Current.Value; // The type of emaValue is decimal
If the type or the member name is incorrect, the Get<T>
method causes a runtime error.
Other Types
You can only add properties to the Security
type.
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.
public class DuckTypingAlgorithm : QCAlgorithm { private Universe _universe; public override void Initialize() { SetStartDate(2022, 1, 1); SetEndDate(2022, 6, 1); // Weekly renewal on the universe to allow time to capitalize on the trend. UniverseSettings.Schedule.On(DateRules.WeekStart()); // Trade on the top 20 liquid equities since they have higher volume and price movements. _universe = AddUniverse(Universe.DollarVolume.Top(20)); } public override void OnData(Slice slice) { foreach (var (symbol, security) in _universe.Members) { // Trade on updated price data. if (slice.Bars.TryGetValue(symbol, out var bar)) { var ema = (security as dynamic).ema; // Switch to long if the current price is above EMA, suggesting an uptrend. if (bar.Close > ema && !Portfolio[symbol].IsLong) { SetHoldings(symbol, 0.025m); } // Switch to short if the current price is below EMA, suggesting a downtrend. else if (bar.Close < ema && !Portfolio[symbol].IsShort) { SetHoldings(symbol, -0.025m); } } } } public override void OnSecuritiesChanged(SecurityChanges changes) { foreach (var removed in changes.RemovedSecurities) { // Liquidate if it is no longer in the top 50 liquidity stocks. Liquidate(removed.Symbol); // Release resources that update automatic indicators DeregisterIndicator(removed.Get("ema")); } foreach (var added in changes.AddedSecurities) { // 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. var security = added as dynamic; security.ema = EMA(added.Symbol, 60, Resolution.Daily); // Warm up the EMA indicator to ensure its readiness for immediate usage. WarmUpIndicator(added.Symbol, (ExponentialMovingAverage)security.ema, Resolution.Daily); } } }
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)