/// <summary> /// /// </summary> /// <returns>The last contract used in the construction of this continuous futures instrument.</returns> private Instrument GetContFutData(HistoricalDataRequest request, bool raiseDataEvent = true) { //copy over the list of contracts that we're gonna be using List <Instrument> futures = new List <Instrument>(_contracts[request.AssignedID]); //start by cleaning up the data, it is possible that some of the futures may not have had ANY data returned! lock (_dataLock) { futures = futures.Where(x => _data[new KeyValuePair <int, BarSize>(x.ID.Value, request.Frequency)].Count > 0).ToList(); } if (futures.Count == 0) { Log(LogLevel.Warn, "No data found"); if (raiseDataEvent) { RaiseEvent(HistoricalDataArrived, this, new HistoricalDataEventArgs(request, new List <OHLCBar>())); } return(null); } var cf = request.Instrument.ContinuousFuture; Instrument frontFuture = futures.FirstOrDefault(); Instrument backFuture = futures.ElementAt(1); //sometimes the contract will be based on the Xth month //this is where we keep track of the actual contract currently being used Instrument selectedFuture = futures.ElementAt(cf.Month - 1); Instrument lastUsedSelectedFuture = selectedFuture; //final date is the earliest of: the last date of data available, or the request's endingdate DateTime lastDateAvailable = new DateTime(1, 1, 1); TimeSeries frontData, backData, selectedData; lock (_dataLock) { frontData = new TimeSeries(_data[new KeyValuePair <int, BarSize>(frontFuture.ID.Value, request.Frequency)]); backData = new TimeSeries(_data[new KeyValuePair <int, BarSize>(backFuture.ID.Value, request.Frequency)]); selectedData = new TimeSeries(_data[new KeyValuePair <int, BarSize>(selectedFuture.ID.Value, request.Frequency)]); lastDateAvailable = _data[new KeyValuePair <int, BarSize>(futures.Last().ID.Value, request.Frequency)].Last().DT; } DateTime finalDate = request.EndingDate < lastDateAvailable ? request.EndingDate : lastDateAvailable; //This is a super dirty hack to make non-time based rollovers actually work. //The reason is that the starting point will otherwise be a LONG time before the date we're interested in. //And at that time both the front and back futures are really far from expiration. //As such volumes can be wonky, and thus result in a rollover far before we ACTUALLY would //want to roll over if we had access to even earlier data. DateTime currentDate = frontFuture.Expiration.Value.AddDays(-20 - cf.RolloverDays); frontData.AdvanceTo(currentDate); backData.AdvanceTo(currentDate); selectedData.AdvanceTo(currentDate); List <OHLCBar> cfData = new List <OHLCBar>(); Calendar calendar = MyUtils.GetCalendarFromCountryCode("US"); bool switchContract = false; int counter = 0; //some rollover rules require multiple consecutive days of greater vol/OI...this keeps track of that List <long> frontDailyVolume = new List <long>(); //keeps track of how much volume has occured in each day List <int> frontDailyOpenInterest = new List <int>(); //keeps track of open interest on a daily basis List <long> backDailyVolume = new List <long>(); List <int> backDailyOpenInterest = new List <int>(); long frontTodaysVolume = 0, backTodaysVolume = 0; //add the first piece of data we have available, and start looping cfData.Add(selectedData[0]); //the first time we go from one day to the next we don't want to check for switching conditions //because we need to ensure that we use an entire day's worth of volume data. bool firstDaySwitchover = true; while (currentDate < finalDate) { //keep track of total volume "today" if (frontData[0].Volume.HasValue) { frontTodaysVolume += frontData[0].Volume.Value; } if (backData != null && backData[0].Volume.HasValue) { backTodaysVolume += backData[0].Volume.Value; } if (frontData.CurrentBar > 0 && frontData[0].DT.Day != frontData[1].DT.Day) { if (firstDaySwitchover) { firstDaySwitchover = false; frontTodaysVolume = 0; backTodaysVolume = 0; } frontDailyVolume.Add(frontTodaysVolume); backDailyVolume.Add(backTodaysVolume); if (frontData[0].OpenInterest.HasValue) { frontDailyOpenInterest.Add(frontData[0].OpenInterest.Value); } if (backData != null && backData[0].OpenInterest.HasValue) { backDailyOpenInterest.Add(backData[0].OpenInterest.Value); } frontTodaysVolume = 0; backTodaysVolume = 0; //do we need to switch contracts? switch (cf.RolloverType) { case ContinuousFuturesRolloverType.Time: if (MyUtils.BusinessDaysBetween(currentDate, frontFuture.Expiration.Value, calendar) <= cf.RolloverDays) { switchContract = true; } break; case ContinuousFuturesRolloverType.Volume: if (backData != null && backDailyVolume.Last() > frontDailyVolume.Last()) { counter++; } else { counter = 0; } switchContract = counter >= cf.RolloverDays; break; case ContinuousFuturesRolloverType.OpenInterest: if (backData != null && backDailyOpenInterest.Last() > frontDailyOpenInterest.Last()) { counter++; } else { counter = 0; } switchContract = counter >= cf.RolloverDays; break; case ContinuousFuturesRolloverType.VolumeAndOpenInterest: if (backData != null && backDailyOpenInterest.Last() > frontDailyOpenInterest.Last() && backDailyVolume.Last() > frontDailyVolume.Last()) { counter++; } else { counter = 0; } switchContract = counter >= cf.RolloverDays; break; case ContinuousFuturesRolloverType.VolumeOrOpenInterest: if (backData != null && backDailyOpenInterest.Last() > frontDailyOpenInterest.Last() || backDailyVolume.Last() > frontDailyVolume.Last()) { counter++; } else { counter = 0; } switchContract = counter >= cf.RolloverDays; break; } } if (frontFuture.Expiration.Value <= currentDate) { //no matter what, obviously we need to switch if the contract expires switchContract = true; } //finally if we have simply run out of data, we're forced to switch if (frontData.ReachedEndOfSeries) { switchContract = true; } //finally advance the time and indices...keep moving forward until the selected series has moved frontData.NextBar(); currentDate = frontData[0].DT; if (backData != null) { backData.AdvanceTo(currentDate); } selectedData.AdvanceTo(currentDate); //this next check here is necessary for the time-based switchover to work after weekends or holidays if (cf.RolloverType == ContinuousFuturesRolloverType.Time && (frontFuture.Expiration.Value < currentDate || MyUtils.BusinessDaysBetween(currentDate, frontFuture.Expiration.Value, calendar) <= cf.RolloverDays)) { switchContract = true; } //we switch to the next contract if (switchContract) { //make any required price adjustments decimal adjustmentFactor; if (cf.AdjustmentMode == ContinuousFuturesAdjustmentMode.Difference) { adjustmentFactor = backData[0].Close - frontData[0].Close; foreach (OHLCBar bar in cfData) { AdjustBar(bar, adjustmentFactor, cf.AdjustmentMode); } } else if (cf.AdjustmentMode == ContinuousFuturesAdjustmentMode.Ratio) { adjustmentFactor = backData[0].Close / frontData[0].Close; foreach (OHLCBar bar in cfData) { AdjustBar(bar, adjustmentFactor, cf.AdjustmentMode); } } //update the contracts var prevFront = frontFuture; frontFuture = backFuture; backFuture = futures.FirstOrDefault(x => x.Expiration > backFuture.Expiration); var prevSelected = selectedFuture; selectedFuture = futures.Where(x => x.Expiration >= frontFuture.Expiration).ElementAtOrDefault(cf.Month - 1); Log(LogLevel.Info, string.Format("CFB Filling request for {0}: switching front contract from {1} to {2} (selected contract from {3} to {4}) at {5}", request.Instrument.Symbol, prevFront.Symbol, frontFuture.Symbol, prevSelected.Symbol, selectedFuture == null ? "" : selectedFuture.Symbol, currentDate.ToString("yyyy-MM-dd"))); if (frontFuture == null) { break; //no other futures left, get out } if (selectedFuture == null) { break; } lock (_dataLock) { frontData = new TimeSeries(_data[new KeyValuePair <int, BarSize>(frontFuture.ID.Value, request.Frequency)]); backData = backFuture != null ? new TimeSeries(_data[new KeyValuePair <int, BarSize>(backFuture.ID.Value, request.Frequency)]) : null; selectedData = new TimeSeries(_data[new KeyValuePair <int, BarSize>(selectedFuture.ID.Value, request.Frequency)]); } frontData.AdvanceTo(currentDate); if (backData != null) { backData.AdvanceTo(currentDate); } selectedData.AdvanceTo(currentDate); //TODO make sure that the data series actually cover the current date switchContract = false; lastUsedSelectedFuture = selectedFuture; } cfData.Add(selectedData[0]); } //clean up _contracts.Remove(request.AssignedID); //throw out any data from before the start of the request cfData = cfData.Where(x => x.DT >= request.StartingDate && x.DT <= request.EndingDate).ToList(); //we're done, so just raise the event if (raiseDataEvent) { RaiseEvent(HistoricalDataArrived, this, new HistoricalDataEventArgs(request, cfData)); } //clean up some data! lock (_dataUsesLock) { foreach (Instrument i in futures) { var kvp = new KeyValuePair <int, BarSize>(i.ID.Value, request.Frequency); if (_dataUsesPending[kvp] == 1) //this data isn't needed anywhere else, we can delete it { _dataUsesPending.Remove(kvp); lock (_dataLock) { _data.Remove(kvp); } } else { _dataUsesPending[kvp]--; } } } return(lastUsedSelectedFuture); }
public void BusinessDaysBetweenReturnsCorrectNumberOfDays() { Assert.AreEqual(250, MyUtils.BusinessDaysBetween(new DateTime(2002, 12, 15), new DateTime(2003, 12, 15), new UnitedStates())); Assert.AreEqual(18, MyUtils.BusinessDaysBetween(new DateTime(2003, 12, 15), new DateTime(2004, 1, 12), new UnitedStates())); Assert.AreEqual(0, MyUtils.BusinessDaysBetween(new DateTime(2003, 12, 15), new DateTime(2003, 12, 15), new UnitedStates())); }
public void BusinessDaysBetweenThrowsExceptionWhenEndDateBeforeStartDate() { MyUtils.BusinessDaysBetween(new DateTime(2000, 1, 1), new DateTime(1999, 1, 1), new UnitedStates()); }
public void BusinessDaysBetweenThrowsExceptionWhenEndDateBeforeStartDate() { Assert.That( () => MyUtils.BusinessDaysBetween(new DateTime(2000, 1, 1), new DateTime(1999, 1, 1), new UnitedStates()), Throws.TypeOf <Exception>()); }