/// <summary> /// Determines whether or not fill forward is required, and if true, will produce the new fill forward data /// </summary> /// <param name="fillForwardResolution"></param> /// <param name="previous">The last piece of data emitted by this enumerator</param> /// <param name="next">The next piece of data on the source enumerator, this may be null</param> /// <param name="fillForward">When this function returns true, this will have a non-null value, null when the function returns false</param> /// <returns>True when a new fill forward piece of data was produced and should be emitted by this enumerator</returns> protected override bool RequiresFillForwardData(TimeSpan fillForwardResolution, BaseData previous, BaseData next, out BaseData fillForward) { fillForward = null; var nextExpectedDataPointTime = (previous.EndTime + fillForwardResolution); if (next != null) { // if not future data, just return the 'next' if (next.EndTime <= nextExpectedDataPointTime) { return(false); } // next is future data, fill forward in between var clone = previous.Clone(true); clone.Time = previous.Time + fillForwardResolution; fillForward = clone; return(true); } // the underlying enumerator returned null, check to see if time has passed for fill fowarding var currentLocalTime = _timeProvider.GetUtcNow().ConvertFromUtc(Exchange.TimeZone); if (nextExpectedDataPointTime <= currentLocalTime) { var clone = previous.Clone(true); clone.Time = previous.Time + fillForwardResolution; fillForward = clone; return(true); } return(false); }
/// <summary> /// Determines whether or not fill forward is required, and if true, will produce the new fill forward data /// </summary> /// <param name="fillForwardResolution"></param> /// <param name="previous">The last piece of data emitted by this enumerator</param> /// <param name="next">The next piece of data on the source enumerator, this may be null</param> /// <param name="fillForward">When this function returns true, this will have a non-null value, null when the function returns false</param> /// <returns>True when a new fill forward piece of data was produced and should be emitted by this enumerator</returns> protected override bool RequiresFillForwardData(TimeSpan fillForwardResolution, BaseData previous, BaseData next, out BaseData fillForward) { // convert times to UTC for accurate comparisons and differences across DST changes fillForward = null; // Add a delay to the time we expect a data point if we've configured a delay for batching var nextExpectedDataPointTimeUtc = previous.EndTime.ConvertToUtc(Exchange.TimeZone) + fillForwardResolution; if (next != null) { // if not future data, just return the 'next' if (next.EndTime.ConvertToUtc(Exchange.TimeZone) <= nextExpectedDataPointTimeUtc) { return(false); } // next is future data, fill forward in between var clone = previous.Clone(true); clone.Time = previous.Time + fillForwardResolution; fillForward = clone; return(true); } // the underlying enumerator returned null, check to see if time has passed for fill forwarding if (nextExpectedDataPointTimeUtc <= _timeProvider.GetUtcNow()) { var clone = previous.Clone(true); clone.Time = previous.Time + fillForwardResolution; fillForward = clone; return(true); } return(false); }
/// <summary> /// Clones the data, computes the utc emit time and performs exchange round down behavior, storing the result in a new <see cref="SubscriptionData"/> instance /// </summary> /// <param name="configuration">The subscription's configuration</param> /// <param name="exchangeHours">The exchange hours of the security</param> /// <param name="offsetProvider">The subscription's offset provider</param> /// <param name="data">The data being emitted</param> /// <param name="normalizationMode">Specifies how data is normalized</param> /// <param name="factor">price scale factor</param> /// <returns>A new <see cref="SubscriptionData"/> containing the specified data</returns> public static SubscriptionData Create(SubscriptionDataConfig configuration, SecurityExchangeHours exchangeHours, TimeZoneOffsetProvider offsetProvider, BaseData data, DataNormalizationMode normalizationMode, decimal?factor = null) { if (data == null) { return(null); } data = data.Clone(data.IsFillForward); var emitTimeUtc = offsetProvider.ConvertToUtc(data.EndTime); // Let's round down for any data source that implements a time delta between // the start of the data and end of the data (usually used with Bars). // The time delta ensures that the time collected from `EndTime` has // no look-ahead bias, and is point-in-time. if (data.Time != data.EndTime) { data.Time = data.Time.ExchangeRoundDownInTimeZone(configuration.Increment, exchangeHours, configuration.DataTimeZone, configuration.ExtendedMarketHours); } if (factor.HasValue && (configuration.SecurityType != SecurityType.Equity || (factor.Value != 1 || configuration.SumOfDividends != 0))) { var normalizedData = data.Clone(data.IsFillForward).Normalize(factor.Value, normalizationMode, configuration.SumOfDividends); return(new PrecalculatedSubscriptionData(configuration, data, normalizedData, normalizationMode, emitTimeUtc)); } return(new SubscriptionData(data, emitTimeUtc)); }
/// <summary> /// Dequeues the current working bar /// </summary> /// <param name="utcTriggerTime">The current trigger time in UTC</param> /// <returns>The base data instance, or null, if no data is to be emitted</returns> public BaseData TriggerArchive(DateTime utcTriggerTime) { BaseData bar; if (!_queue.TryDequeue(out bar)) { // if a bar wasn't ready, check for fill forward if (_previous != null && _config.FillDataForward) { // exchanges hours are in local time, so convert to local before checking if exchange is open var localTriggerTime = utcTriggerTime.ConvertFromUtc(_config.TimeZone); // only perform fill forward behavior if the exchange is considered open var barStartTime = localTriggerTime - _increment; if (_security.Exchange.IsOpenDuringBar(barStartTime, localTriggerTime, _config.ExtendedMarketHours)) { bar = _previous.Clone(true); bar.Time = barStartTime.ExchangeRoundDown(_increment, _security.Exchange.Hours, _security.IsExtendedMarketHours); } } } // we don't have data, so just return null if (bar == null) { return(null); } // reset the previous bar for fill forward _previous = bar.Clone(); return(bar); }
/// <summary> /// A time period has lapsed, trigger a save/queue of the current value of data. /// </summary> /// <param name="utcTriggerTime">The time we're triggering this archive for</param> /// <param name="fillForward">Data stream is a fillforward type</param> public void TriggerArchive(DateTime utcTriggerTime, bool fillForward) { var localTriggerTime = utcTriggerTime.ConvertTo(TimeZones.Utc, _config.TimeZone); lock (_lock) { try { //When there's nothing to do: if (_data == null && !fillForward) { Log.Debug("StreamStore.TriggerArchive(): No data to store, and not fill forward: " + Symbol); } if (_data != null) { //Create clone and reset original Log.Debug("StreamStore.TriggerArchive(): Enqueued new data: S:" + _data.Symbol + " V:" + _data.Value); _previousData = _data.Clone(); _queue.Enqueue(_data.Clone()); _data = null; } else if (fillForward && _data == null && _previousData != null) { // the time is actually the end time of a bar, check to see if the start time // is within market hours, which is really just checking the _previousData's EndTime if (!_security.Exchange.IsOpenDuringBar(localTriggerTime - _increment, localTriggerTime, _config.ExtendedMarketHours)) { Log.Debug("StreamStore.TriggerArchive(): Exchange is closed: " + Symbol); return; } //There was no other data in this timer period, and this is a fillforward subscription: Log.Debug("StreamStore.TriggerArchive(): Fillforward, Previous Enqueued: S:" + _previousData.Symbol + " V:" + _previousData.Value); var cloneForward = _previousData.Clone(true); cloneForward.Time = _previousData.Time.Add(_increment); _queue.Enqueue(cloneForward); _previousData = cloneForward.Clone(); } } catch (Exception err) { Log.Error("StreamStore.TriggerAchive(fillforward): Failed to archive: " + err.Message); } } }
/// <summary> /// Clones the data, computes the utc emit time and performs exchange round down behavior, storing the result in a new <see cref="SubscriptionData"/> instance /// </summary> /// <param name="configuration">The subscription's configuration</param> /// <param name="exchangeHours">The exchange hours of the security</param> /// <param name="offsetProvider">The subscription's offset provider</param> /// <param name="data">The data being emitted</param> /// <returns>A new <see cref="SubscriptionData"/> containing the specified data</returns> public static SubscriptionData Create(SubscriptionDataConfig configuration, SecurityExchangeHours exchangeHours, TimeZoneOffsetProvider offsetProvider, BaseData data) { if (data == null) { return(null); } data = data.Clone(data.IsFillForward); var emitTimeUtc = offsetProvider.ConvertToUtc(data.EndTime); data.Time = data.Time.ExchangeRoundDownInTimeZone(configuration.Increment, exchangeHours, configuration.DataTimeZone, configuration.ExtendedMarketHours); return(new SubscriptionData(data, emitTimeUtc)); }
} // End of Update /// <summary> /// A time period has lapsed, trigger a save/queue of the current value of data. /// </summary> /// <param name="fillForward">Data stream is a fillforward type</param> public void TriggerArchive(bool fillForward) { lock (_lock) { try { //When there's nothing to do: if (_data == null && !fillForward) { Log.Debug("StreamStore.TriggerArchive(): No data to store, and not fill forward: " + Symbol); } if (_data != null) { //Create clone and reset original Log.Debug("StreamStore.TriggerArchive(): Enqueued new data: S:" + _data.Symbol + " V:" + _data.Value); _previousData = _data.Clone(); _queue.Enqueue(_data.Clone()); _data = null; } else if (fillForward && _data == null && _previousData != null) { //There was no other data in this timer period, and this is a fillforward subscription: Log.Debug("StreamStore.TriggerArchive(): Fillforward, Previous Enqueued: S:" + _previousData.Symbol + " V:" + _previousData.Value); var cloneForward = _previousData.Clone(true); cloneForward.Time = _previousData.Time.Add(_increment); _queue.Enqueue(cloneForward); _previousData = cloneForward.Clone(); } } catch (Exception err) { Log.Error("StreamStore.TriggerAchive(fillforward): Failed to archive: " + err.Message); } } }
/// <summary> /// 複製一份資料。 /// </summary> /// <returns></returns> internal ConfigurationRecord Clone() { ConfigurationRecord record = new ConfigurationRecord(Namespace); record.EditAction = EditAction; if (BaseData != null) { record.BaseData = BaseData.Clone(); } if (PreviousData != null) { record.PreviousData = PreviousData.CloneNode(true) as XmlElement; } return(record); }
/// <summary> /// Clones the data, computes the utc emit time and performs exchange round down behavior, storing the result in a new <see cref="SubscriptionData"/> instance /// </summary> /// <param name="configuration">The subscription's configuration</param> /// <param name="exchangeHours">The exchange hours of the security</param> /// <param name="offsetProvider">The subscription's offset provider</param> /// <param name="data">The data being emitted</param> /// <returns>A new <see cref="SubscriptionData"/> containing the specified data</returns> public static SubscriptionData Create(SubscriptionDataConfig configuration, SecurityExchangeHours exchangeHours, TimeZoneOffsetProvider offsetProvider, BaseData data) { if (data == null) { return(null); } data = data.Clone(data.IsFillForward); var emitTimeUtc = offsetProvider.ConvertToUtc(data.EndTime); // Let's round down for any data source that implements a time delta between // the start of the data and end of the data (usually used with Bars). // The time delta ensures that the time collected from `EndTime` has // no look-ahead bias, and is point-in-time. if (data.Time != data.EndTime) { data.Time = data.Time.ExchangeRoundDownInTimeZone(configuration.Increment, exchangeHours, configuration.DataTimeZone, configuration.ExtendedMarketHours); } return(new SubscriptionData(data, emitTimeUtc)); }