Indicators
Custom Indicators
Prerequisites
Working knowledge of C#.
If you use Python, you must understand how to work with pandas DataFrames and Series. If you are not familiar with pandas, refer to the pandas documentation.
Get Historical Data
Get some historical market data to warm-up and create a historical record of indicator values. For example, to get data for SPY, run:
var qb = new QuantBook(); var symbol = qb.AddEquity("SPY").Symbol; var history = qb.History(symbol, 70, Resolution.Daily);
qb = QuantBook() symbol = qb.AddEquity("SPY").Symbol history = qb.History(symbol, 70, Resolution.Daily).loc[symbol]
Create Indicator Timeseries
Follow these steps to create an indicator timeseries:
- Define a custom indicator class, inherited from the
Indicator
superclass. - Define a custom indicator class. Note that the
PythonIndicator
superclass inheritance,Value
attribute andUpdate
method is mandatory. - Initialize a new instance of the custom indicator. In this tutorial, 50-period 5%
ExpectedShortfallPercent
indicator is used. - Create a
RollingWindow
for each attribute of the indicator to hold their values. - Set handler methods to update the
RollingWindow
s. - Iterate the historical market data to update the indicators and the
RollingWindow
s. - Display the data.
- Convert the
RollingWindow
s' data intopandas.DataFrame
.
In this tutorial, we're creating an ExpectedShortfallPercent indicator from Monte Carlo method to calculate the expected shortfall in return. We'll be using WindowIndicator
superclass instead of Indicator
for using a period of historical data stored in a RollingWindow
.
In this tutorial, we're creating an ExpectedShortfallPercent
indicator from Monte Carlo method to calculate the expected shortfall in return.
public class ExpectedShortfallPercent : WindowIndicator<IndicatorDataPoint>, IIndicatorWarmUpPeriodProvider { private decimal _alpha; // Set up IndicatorDataPoint attributes for the indicator. public IndicatorBase<IndicatorDataPoint> ValueAtRisk { get; } // Set up the WarmUpPeriod attribute to provide implementation of the IIndicatorWarmUpPeriodProvider interface. public override int WarmUpPeriod => Period; // Set up the constructor. // period: The lookback period for return distribution. // alpha: Alpha level of VaR cutoff. public ExpectedShortfallPercent(int period, decimal alpha) : base("ES", period) { _alpha = alpha; ValueAtRisk = new Identity("ES_VaR"); } // Override the IsReady method to set up the flag of the Indicator and its IndicatorDataPoint attributes are ready. public override bool IsReady => ValueAtRisk.IsReady; // Mandatory: Override the ComputeNextValue method to calculate the indictor value. protected override decimal ComputeNextValue(IReadOnlyWindow<IndicatorDataPoint> window, IndicatorDataPoint input) { if (Samples < 2) return 0m; var n = Math.Min(Period, Samples); var cutoff = (int) Math.Ceiling(n * _alpha); var samples = new List<decimal>(); for (int i = 0; i < window.Count - 1; i++) { samples.Add( (window[i] - window[i+1]) / window[i+1] ); } var lowest = samples.OrderBy(x => x).Take(cutoff); ValueAtRisk.Update(input.Time, lowest.Last()); return lowest.Average(); } }
class ExpectedShortfallPercent(PythonIndicator): import math, numpy as np def __init__(self, period, alpha): self.Value = None # Attribute represents the indicator value self.ValueAtRisk = None self.alpha = alpha self.window = RollingWindow[float](period) # Override the IsReady attribute to flag all attributes values are ready. @property def IsReady(self) -> bool: return self.Value and self.ValueAtRisk # Method to update the indicator values. Note that it only receives 1 IBaseData object (Tick, TradeBar, QuoteBar) argument. def Update(self, input: BaseData) -> bool: count = self.window.Count self.window.Add(input.Close) # Update the Value and other attributes as the indicator current value. if count >= 2: cutoff = math.ceil(self.alpha * count) ret = [ (self.window[i] - self.window[i+1]) / self.window[i+1] for i in range(count-1) ] lowest = sorted(ret)[:cutoff] self.Value = np.mean(lowest) self.ValueAtRisk = lowest[-1] # return a boolean to indicate IsReady. return count >= 2
var es = new ExpectedShortfallPercent(50, 0.05m);
custom = ExpectedShortfallPercent(50, 0.05)
In this example, save 20 data points.
var time = new RollingWindow<DateTime>(20); var window = new Dictionary<string, RollingWindow<decimal>>(); window["expectedshortfall"] = new RollingWindow<decimal>(20); window["valueatrisk"] = new RollingWindow<decimal>(20);
window = {} window['time'] = RollingWindow[DateTime](20) window['expectedshortfall'] = RollingWindow[float](20) window['valueatrisk'] = RollingWindow[float](20)
es.Updated += (sender, updated) => { var indicator = (ExpectedShortfallPercent) sender; time.Add(updated.EndTime); window["expectedshortfall"].Add(updated); window["valueatrisk"].Add(indicator.ValueAtRisk.Current); };
When the indicators receive new data, the handler will add the new IndicatorDataPoints
into the RollingWindow
s.
foreach(var bar in history){ es.Update(bar.EndTime, bar.Close); }
for time, row in history.iterrows(): # The Update method's input must be IBaseData object (Tick, TradeBar, QuoteBar). bar = TradeBar(time, symbol, row.open, row.high, row.low, row.close, row.volume) custom.Update(bar) # The Updated event handler is not available for custom indicator in Python, RollingWindows are needed to be updated in here. if custom.IsReady: window['time'].Add(bar.EndTime) window['expectedshortfall'].Add(custom.Value) window['valueatrisk'].Add(custom.ValueAtRisk)
Console.WriteLine($"time,{string.Join(',', window.Select(kvp => kvp.Key))}"); foreach (var i in Enumerable.Range(0, 5).Reverse()) { var data = string.Join(", ", window.Select(kvp => Math.Round(kvp.Value[i],6))); Console.WriteLine($"{time[i]:yyyyMMdd}, {data}"); }

custom_dataframe = pd.DataFrame(window).set_index('time'))

Indicator Helper Method
Jupyter Notebooks don't currently support the qb.Indicator
helper method for custom indicator. Please subscribe to this GitHub issue for update.