/// <summary> /// Calculate MACD, as described here: /// <see href="https://en.wikipedia.org/wiki/MACD"/> /// </summary> /// <param name="series">input time series</param> /// <param name="fast">fast EMA period</param> /// <param name="slow">slow EMA period</param> /// <param name="signal">signal line period</param> /// <param name="parentId">caller cache id, optional</param> /// <param name="memberName">caller's member name, optional</param> /// <param name="lineNumber">caller line number, optional</param> /// <returns></returns> public static _MACD MACD(this ITimeSeries <double> series, int fast = 12, int slow = 26, int signal = 9, CacheId parentId = null, [CallerMemberName] string memberName = "", [CallerLineNumber] int lineNumber = 0) { var cacheId = new CacheId(parentId, memberName, lineNumber, series.GetHashCode(), fast, slow, signal); var container = Cache <_MACD> .GetData( cacheId, () => new _MACD()); container.Fast = series.EMA(fast, cacheId); container.Slow = series.EMA(slow, cacheId); container.MACD = container.Fast.Subtract(container.Slow, cacheId); container.Signal = container.MACD.EMA(signal, cacheId); container.Divergence = container.MACD.Subtract(container.Signal, cacheId); return(container); }
/// <summary> /// Calculate Double Exponential Moving Average, as described here: /// <see href="https://en.wikipedia.org/wiki/Double_exponential_moving_average"/> /// </summary> /// <param name="series">input time series</param> /// <param name="n">averaging length</param> /// <param name="parentId">caller cache id, optional</param> /// <param name="memberName">caller's member name, optional</param> /// <param name="lineNumber">caller line number, optional</param> /// <returns>DEMA time series</returns> public static ITimeSeries <double> DEMA(this ITimeSeries <double> series, int n, CacheId parentId = null, [CallerMemberName] string memberName = "", [CallerLineNumber] int lineNumber = 0) { var cacheId = new CacheId(parentId, memberName, lineNumber, series.GetHashCode(), n); return(series .Multiply(2.0, cacheId) .EMA(n, cacheId) .Subtract( series .EMA(n, cacheId) .EMA(n, cacheId), cacheId)); }
override public void Run() { //---------- initialization // set simulation time frame StartTime = DateTime.Parse("01/01/2015"); EndTime = DateTime.Parse("12/31/2016"); // add instruments AddDataSource(_instrumentNick); //---------- simulation foreach (DateTime simTime in SimTimes) { // find our instrument. if we have only one instrument, // we could also just use Instruments.Values.First() Instrument instrument = FindInstrument(_instrumentNick); // calculate simple indicators // the output of an indicator is a time series ITimeSeries <double> ema26 = instrument.Close.EMA(26); ITimeSeries <double> ema12 = instrument.Close.EMA(12); // therefore, indicators can be calculated on top of indicators ITimeSeries <double> macd = ema12.Subtract(ema26); ITimeSeries <double> signal = macd.EMA(9); // plot our data _plotter.SelectChart("indicators vs time", "date"); _plotter.SetX(simTime.Date); _plotter.Plot(instrument.Symbol, instrument.Close[0] + _offsetPrice); _plotter.Plot("ema26", ema26[0] + _offsetPrice); _plotter.Plot("ema12", ema12[0] + _offsetPrice); _plotter.Plot("macd", macd[0]); _plotter.Plot("signal", signal[0]); } }
/// <summary> /// Calculate True Strength Index of input time series, as described here: /// <see href="https://en.wikipedia.org/wiki/True_strength_index"/> /// </summary> /// <param name="series">input time series</param> /// <param name="r">smoothing period for momentum</param> /// <param name="s">smoothing period for smoothed momentum</param> /// <param name="parentId">caller cache id, optional</param> /// <param name="memberName">caller's member name, optional</param> /// <param name="lineNumber">caller line number, optional</param> /// <returns>TSI time series</returns> public static ITimeSeries <double> TSI(this ITimeSeries <double> series, int r = 25, int s = 13, CacheId parentId = null, [CallerMemberName] string memberName = "", [CallerLineNumber] int lineNumber = 0) { var cacheId = new CacheId(parentId, memberName, lineNumber, series.GetHashCode(), r, s); ITimeSeries <double> momentum = series .Return(cacheId); double numerator = momentum .EMA(r, cacheId) .EMA(s, cacheId)[0]; double denominator = momentum .AbsValue(cacheId) .EMA(r, cacheId) .EMA(s, cacheId)[0]; return(IndicatorsBasic.BufferedLambda( v => 100.0 * numerator / Math.Max(1e-10, denominator), 0.5, cacheId)); }
/// <summary> /// Normalize time series over number of bars; 1.0 being the average. /// </summary> /// <param name="series">input time series</param> /// <param name="n">normalizing period</param> /// <returns>normalized time series</returns> public static ITimeSeries <double> Normalize(this ITimeSeries <double> series, int n) { return(series.Divide(series.EMA(n))); }
override public void Run() { //---------- initialization // set simulation time frame StartTime = DateTime.Parse("01/01/2017", CultureInfo.InvariantCulture); EndTime = DateTime.Parse("08/01/2018", CultureInfo.InvariantCulture); // set account value Deposit(_initialCash); CommissionPerShare = 0.01; // add instruments // the underlying must be added explicitly, // as the simulation engine requires it AddDataSource(_underlyingNickname); AddDataSource(_optionsNickname); //---------- simulation // loop through all bars foreach (DateTime simTime in SimTimes) { // find the underlying instrument // we could also find the underlying from the option chain _underlyingInstrument = _underlyingInstrument ?? FindInstrument(_underlyingNickname); // retrieve the underlying spot price double underlyingPrice = _underlyingInstrument.Close[0]; _initialUnderlyingPrice = _initialUnderlyingPrice ?? underlyingPrice; // calculate volatility ITimeSeries <double> volatilitySeries = _underlyingInstrument.Close .Volatility(10) .Multiply(Math.Sqrt(252.0)); double volatility = Math.Max( volatilitySeries.EMA(21)[0], volatilitySeries.Highest(5)[0]); // find all expiry dates on the 3rd Friday of the month List <DateTime> expiryDates = OptionChain(_optionsNickname) .Where(o => o.OptionExpiry.DayOfWeek == DayOfWeek.Friday && o.OptionExpiry.Day >= 15 && o.OptionExpiry.Day <= 21 || o.OptionExpiry.DayOfWeek == DayOfWeek.Saturday && o.OptionExpiry.Day >= 16 && o.OptionExpiry.Day <= 22) .Select(o => o.OptionExpiry) .Distinct() .ToList(); // select expiry 3 to 4 weeks out DateTime expiryDate = expiryDates .Where(d => (d - simTime).TotalDays >= 21 && (d - simTime).TotalDays <= 28) .FirstOrDefault(); // retrieve option chain for this expiry List <Instrument> optionChain = OptionChain(_optionsNickname) .Where(o => o.OptionIsPut && o.OptionExpiry == expiryDate) .ToList(); // if we are currently flat, attempt to open a position if (Positions.Count == 0) { // determine strike price: far away from spot price double strikePrice = _underlyingInstrument.Close[0] / Math.Exp(1.75 * Math.Sqrt((expiryDate - simTime).TotalDays / 365.25) * volatility); // find contract closest to our desired strike Instrument shortPut = optionChain .OrderBy(o => Math.Abs(o.OptionStrike - strikePrice)) .FirstOrDefault(); // enter short put position if (shortPut != null) { // Interactive Brokers margin requirements for short naked puts: // Put Price + Maximum((15 % * Underlying Price - Out of the Money Amount), // (10 % * Strike Price)) double margin = Math.Max(0.15 * underlyingPrice - Math.Max(0.0, underlyingPrice - shortPut.OptionStrike), 0.10 * underlyingPrice); int contracts = (int)Math.Floor(Math.Max(0.0, _regTMarginToUse * Cash / (100.0 * margin))); shortPut.Trade(-contracts, OrderType.closeThisBar) .Comment = "open"; } } // monitor and maintain existing positions else // if (Postions.Count != 0) { // find our currently open position // we might need fancier code, in case we have more than // one position open Instrument shortPut = Positions.Keys.First(); // re-evaluate the likely trading range double expectedLowestPrice = _underlyingInstrument.Close[0] / Math.Exp(0.60 * Math.Sqrt((shortPut.OptionExpiry - simTime).Days / 365.25) * volatility); // exit, when the risk of ending in the money is too high // and, the contract is actively traded if (expectedLowestPrice < shortPut.OptionStrike && shortPut.BidVolume[0] > 0 && shortPut.Ask[0] < 2 * shortPut.Bid[0]) { shortPut.Trade(-Positions[shortPut], OrderType.closeThisBar) .Comment = "exit early"; } } // plot the underlying against our strategy results, plus volatility _plotter.SelectChart("nav vs time", "time"); // this will go to Sheet1 _plotter.SetX(simTime); _plotter.Plot(_underlyingInstrument.Symbol, underlyingPrice / (double)_initialUnderlyingPrice); _plotter.Plot("volatility", volatilitySeries[0]); _plotter.Plot("net asset value", NetAssetValue[0] / _initialCash); } //---------- post-processing // create a list of trades on Sheet2 _plotter.SelectChart("trades", "time"); foreach (LogEntry entry in Log) { _plotter.SetX(entry.BarOfExecution.Time); _plotter.Plot("action", entry.Action); _plotter.Plot("instr", entry.Symbol); _plotter.Plot("qty", entry.OrderTicket.Quantity); _plotter.Plot("fill", entry.FillPrice); _plotter.Plot("comment", entry.OrderTicket.Comment ?? ""); } FitnessValue = NetAssetValue[0]; }