/// <summary> /// Primary entry point. /// </summary> public void Run() { IsActive = true; // we want to emit to the bridge minimally once a second since the data feed is // the heartbeat of the application, so this value will contain a second after // the last emit time, and if we pass this time, we'll emit even with no data var nextEmit = DateTime.MinValue; try { while (!_cancellationTokenSource.IsCancellationRequested) { // perform sleeps to wake up on the second? _frontierUtc = _timeProvider.GetUtcNow(); _frontierTimeProvider.SetCurrentTime(_frontierUtc); var data = new List <DataFeedPacket>(); foreach (var subscription in Subscriptions) { var packet = new DataFeedPacket(subscription.Security, subscription.Configuration); // dequeue data that is time stamped at or before this frontier while (subscription.MoveNext() && subscription.Current != null) { packet.Add(subscription.Current); } // if we have data, add it to be added to the bridge if (packet.Count > 0) { data.Add(packet); } // we have new universe data to select based on if (subscription.IsUniverseSelectionSubscription && packet.Count > 0) { var universe = subscription.Universe; // always wait for other thread to sync up if (!_bridge.WaitHandle.WaitOne(Timeout.Infinite, _cancellationTokenSource.Token)) { break; } // assume that if the first item is a base data collection then the enumerator handled the aggregation, // otherwise, load all the the data into a new collection instance var collection = packet.Data[0] as BaseDataCollection ?? new BaseDataCollection(_frontierUtc, subscription.Configuration.Symbol, packet.Data); _changes += _universeSelection.ApplyUniverseSelection(universe, _frontierUtc, collection); } } // check for cancellation if (_cancellationTokenSource.IsCancellationRequested) { return; } // emit on data or if we've elapsed a full second since last emit if (data.Count != 0 || _frontierUtc >= nextEmit) { _bridge.Add(TimeSlice.Create(_frontierUtc, _algorithm.TimeZone, _algorithm.Portfolio.CashBook, data, _changes), _cancellationTokenSource.Token); // force emitting every second nextEmit = _frontierUtc.RoundDown(Time.OneSecond).Add(Time.OneSecond); } // reset our security changes _changes = SecurityChanges.None; // take a short nap Thread.Sleep(1); } } catch (Exception err) { Log.Error(err); _algorithm.RunTimeError = err; } Log.Trace("LiveTradingDataFeed.Run(): Exited thread."); IsActive = false; }
/// <summary> /// Syncs the specifies subscriptions at the frontier time /// </summary> /// <param name="frontier">The time used for syncing, data in the future won't be included in this time slice</param> /// <param name="subscriptions">The subscriptions to sync</param> /// <param name="sliceTimeZone">The time zone of the created slice object</param> /// <param name="cashBook">The cash book, used for creating the cash book updates</param> /// <param name="nextFrontier">The next frontier time as determined by the first piece of data in the future ahead of the frontier. /// This value will equal DateTime.MaxValue when the subscriptions are all finished</param> /// <returns>A time slice for the specified frontier time</returns> public TimeSlice Sync(DateTime frontier, IEnumerable <Subscription> subscriptions, DateTimeZone sliceTimeZone, CashBook cashBook, out DateTime nextFrontier) { var changes = SecurityChanges.None; nextFrontier = DateTime.MaxValue; var earlyBirdTicks = nextFrontier.Ticks; var data = new List <DataFeedPacket>(); SecurityChanges newChanges; do { newChanges = SecurityChanges.None; foreach (var subscription in subscriptions) { if (subscription.EndOfStream) { OnSubscriptionFinished(subscription); continue; } // prime if needed if (subscription.Current == null) { if (!subscription.MoveNext()) { OnSubscriptionFinished(subscription); continue; } } var packet = new DataFeedPacket(subscription.Security, subscription.Configuration); data.Add(packet); var configuration = subscription.Configuration; var offsetProvider = subscription.OffsetProvider; var currentOffsetTicks = offsetProvider.GetOffsetTicks(frontier); while (subscription.Current.EndTime.Ticks - currentOffsetTicks <= frontier.Ticks) { // we want bars rounded using their subscription times, we make a clone // so we don't interfere with the enumerator's internal logic var clone = subscription.Current.Clone(subscription.Current.IsFillForward); clone.Time = clone.Time.ExchangeRoundDown(configuration.Increment, subscription.Security.Exchange.Hours, configuration.ExtendedMarketHours); packet.Add(clone); if (!subscription.MoveNext()) { OnSubscriptionFinished(subscription); break; } } // we have new universe data to select based on if (subscription.IsUniverseSelectionSubscription && packet.Count > 0) { // assume that if the first item is a base data collection then the enumerator handled the aggregation, // otherwise, load all the the data into a new collection instance var collection = packet.Data[0] as BaseDataCollection ?? new BaseDataCollection(frontier, subscription.Configuration.Symbol, packet.Data); newChanges += _universeSelection.ApplyUniverseSelection(subscription.Universe, frontier, collection); } if (subscription.Current != null) { // take the earliest between the next piece of data or the next tz discontinuity earlyBirdTicks = Math.Min(earlyBirdTicks, Math.Min(subscription.Current.EndTime.Ticks - currentOffsetTicks, offsetProvider.GetNextDiscontinuity())); } } changes += newChanges; }while (newChanges != SecurityChanges.None); nextFrontier = new DateTime(Math.Max(earlyBirdTicks, frontier.Ticks), DateTimeKind.Utc); return(TimeSlice.Create(frontier, sliceTimeZone, cashBook, data, changes)); }