chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:02:50 +08:00
commit 0fc60fdcb1
5008 changed files with 910633 additions and 0 deletions
@@ -0,0 +1,115 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using QuantConnect.Data.Market;
using Python.Runtime;
namespace QuantConnect.Data.Consolidators
{
/// <summary>
/// Type capable of consolidating trade bars from any base data instance
/// </summary>
public class BaseDataConsolidator : TradeBarConsolidatorBase<BaseData>
{
/// <summary>
/// Create a new TickConsolidator for the desired resolution
/// </summary>
/// <param name="resolution">The resolution desired</param>
/// <returns>A consolidator that produces data on the resolution interval</returns>
public static BaseDataConsolidator FromResolution(Resolution resolution)
{
return new BaseDataConsolidator(resolution.ToTimeSpan());
}
/// <summary>
/// Creates a consolidator to produce a new 'TradeBar' representing the period
/// </summary>
/// <param name="period">The minimum span of time before emitting a consolidated bar</param>
public BaseDataConsolidator(TimeSpan period)
: base(period)
{
}
/// <summary>
/// Creates a consolidator to produce a new 'TradeBar' representing the last count pieces of data
/// </summary>
/// <param name="maxCount">The number of pieces to accept before emitting a consolidated bar</param>
public BaseDataConsolidator(int maxCount)
: base(maxCount)
{
}
/// <summary>
/// Creates a consolidator to produce a new 'TradeBar' representing the last count pieces of data or the period, whichever comes first
/// </summary>
/// <param name="maxCount">The number of pieces to accept before emitting a consolidated bar</param>
/// <param name="period">The minimum span of time before emitting a consolidated bar</param>
public BaseDataConsolidator(int maxCount, TimeSpan period)
: base(maxCount, period)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="BaseDataConsolidator"/> class
/// </summary>
/// <param name="func">Func that defines the start time of a consolidated data</param>
public BaseDataConsolidator(Func<DateTime, CalendarInfo> func)
: base(func)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="BaseDataConsolidator"/> class
/// </summary>
/// <param name="pyfuncobj">Func that defines the start time of a consolidated data</param>
public BaseDataConsolidator(PyObject pyfuncobj)
: base(pyfuncobj)
{
}
/// <summary>
/// Aggregates the new 'data' into the 'workingBar'. The 'workingBar' will be
/// null following the event firing
/// </summary>
/// <param name="workingBar">The bar we're building, null if the event was just fired and we're starting a new trade bar</param>
/// <param name="data">The new data</param>
protected override void AggregateBar(ref TradeBar workingBar, BaseData data)
{
if (workingBar == null)
{
workingBar = new TradeBar
{
Symbol = data.Symbol,
Time = GetRoundedBarTime(data.Time),
Close = data.Value,
High = data.Value,
Low = data.Value,
Open = data.Value,
DataType = data.DataType,
Value = data.Value
};
}
else
{
//Aggregate the working bar
workingBar.Close = data.Value;
if (data.Value < workingBar.Low) workingBar.Low = data.Value;
if (data.Value > workingBar.High) workingBar.High = data.Value;
}
}
}
}
@@ -0,0 +1,194 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using Python.Runtime;
using System;
using QuantConnect.Data.Market;
namespace QuantConnect.Data.Consolidators
{
/// <summary>
/// Represents a timeless consolidator which depends on the given values. This consolidator
/// is meant to consolidate data into bars that do not depend on time, e.g., RangeBar's.
/// </summary>
public abstract class BaseTimelessConsolidator<T> : ConsolidatorBase
where T : IBaseData
{
/// <summary>
/// Extracts the value from a data instance to be formed into a <see cref="T"/>.
/// </summary>
protected Func<IBaseData, decimal> Selector { get; set; }
/// <summary>
/// Extracts the volume from a data instance. The default value is null which does
/// not aggregate volume per bar.
/// </summary>
protected Func<IBaseData, decimal> VolumeSelector { get; set; }
/// <summary>
/// Bar being created
/// </summary>
protected virtual T CurrentBar { get; set; }
/// <summary>
/// Gets a clone of the data being currently consolidated
/// </summary>
public abstract override IBaseData WorkingData { get; }
/// <summary>
/// Gets the type consumed by this consolidator
/// </summary>
public override Type InputType => typeof(IBaseData);
/// <summary>
/// Gets <see cref="T"/> which is the type emitted in the <see cref="IDataConsolidator.DataConsolidated"/> event.
/// </summary>
public override Type OutputType => typeof(T);
/// <summary>
/// Typed event handler that fires when a new piece of data is produced
/// </summary>
public new event EventHandler<T> DataConsolidated;
/// <summary>
/// Initializes a new instance of the <see cref="BaseTimelessConsolidator{T}" /> class.
/// </summary>
/// <param name="selector">Extracts the value from a data instance to be formed into a new bar which inherits from <see cref="IBaseData"/>. The default
/// value is (x => x.Value) the <see cref="IBaseData.Value"/> property on <see cref="IBaseData"/></param>
/// <param name="volumeSelector">Extracts the volume from a data instance. The default value is null which does
/// not aggregate volume per bar.</param>
protected BaseTimelessConsolidator(Func<IBaseData, decimal> selector = null, Func<IBaseData, decimal> volumeSelector = null)
{
Selector = selector ?? (x => x.Value);
VolumeSelector = volumeSelector ?? (x => x is TradeBar bar ? bar.Volume : (x is Tick tick ? tick.Quantity : 0));
}
/// <summary>
/// Initializes a new instance of the <see cref="BaseTimelessConsolidator{T}" /> class.
/// </summary>
/// <param name="valueSelector">Extracts the value from a data instance to be formed into a new bar which inherits from <see cref="IBaseData"/>. The default
/// value is (x => x.Value) the <see cref="IBaseData.Value"/> property on <see cref="IBaseData"/></param>
/// <param name="volumeSelector">Extracts the volume from a data instance. The default value is null which does
/// not aggregate volume per bar.</param>
protected BaseTimelessConsolidator(PyObject valueSelector, PyObject volumeSelector = null)
: this (TryToConvertSelector(valueSelector, nameof(valueSelector)), TryToConvertSelector(volumeSelector, nameof(volumeSelector)))
{
}
/// <summary>
/// Tries to convert the given python selector to a C# one. If the conversion is not
/// possible it returns null
/// </summary>
/// <param name="selector">The python selector to be converted</param>
/// <param name="selectorName">The name of the selector to be used in case an exception is thrown</param>
/// <exception cref="ArgumentException">This exception will be thrown if it's not possible to convert the
/// given python selector to C#</exception>
private static Func<IBaseData, decimal> TryToConvertSelector(PyObject selector, string selectorName)
{
using (Py.GIL())
{
Func<IBaseData, decimal> resultSelector;
if (selector != null && !selector.IsNone())
{
if (!selector.TrySafeAs(out resultSelector))
{
throw new ArgumentException(
$"Unable to convert parameter {selectorName} to delegate type Func<IBaseData, decimal>");
}
}
else
{
resultSelector = null;
}
return resultSelector;
}
}
/// <summary>
/// Updates this consolidator with the specified data
/// </summary>
/// <param name="data">The new data for the consolidator</param>
public override void Update(IBaseData data)
{
var currentValue = Selector(data);
var volume = VolumeSelector(data);
// If we're already in a bar then update it
if (CurrentBar != null)
{
UpdateBar(data.Time, currentValue, volume);
}
// The state of the CurrentBar could have changed after UpdateBar(),
// then we might need to create a new bar
if (CurrentBar == null)
{
CreateNewBar(data, currentValue, volume);
}
}
/// <summary>
/// Updates the current RangeBar being created with the given data.
/// Additionally, if it's the case, it consolidates the current RangeBar
/// </summary>
/// <param name="time">Time of the given data</param>
/// <param name="currentValue">Value of the given data</param>
/// <param name="volume">Volume of the given data</param>
protected abstract void UpdateBar(DateTime time, decimal currentValue, decimal volume);
/// <summary>
/// Creates a new bar with the given data
/// </summary>
/// <param name="data">The new data for the bar</param>
/// <param name="currentValue">The new value for the bar</param>
/// <param name="volume">The new volume to the bar</param>
protected abstract void CreateNewBar(IBaseData data, decimal currentValue, decimal volume);
/// <summary>
/// Raises the strongly typed DataConsolidated event
/// </summary>
/// <param name="consolidated">The newly consolidated data</param>
protected override void FireDataConsolidated(IBaseData consolidated)
{
DataConsolidated?.Invoke(this, (T)consolidated);
}
/// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
/// <filterpriority>2</filterpriority>
public override void Dispose()
{
DataConsolidated = null;
base.Dispose();
}
/// <summary>
/// Resets the consolidator
/// </summary>
public override void Reset()
{
CurrentBar = default(T);
base.Reset();
}
/// <summary>
/// Scans this consolidator to see if it should emit a bar due to time passing
/// </summary>
/// <param name="currentLocalTime">The current time in the local time zone (same as <see cref="BaseData.Time"/>)</param>
public override void Scan(DateTime currentLocalTime)
{
}
}
}
+173
View File
@@ -0,0 +1,173 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
namespace QuantConnect.Data.Consolidators
{
/// <summary>
/// Helper class that provides <see cref="Func{DateTime,CalendarInfo}"/> used to define consolidation calendar
/// </summary>
public static class Calendar
{
/// <summary>
/// Computes the start of week (previous Monday) of given date/time
/// </summary>
public static Func<DateTime, CalendarInfo> Weekly
{
get
{
return dt =>
{
var start = Expiry.EndOfWeek(dt).AddDays(-7);
return new CalendarInfo(start, TimeSpan.FromDays(7));
};
}
}
/// <summary>
/// Computes the start of month (1st of the current month) of given date/time
/// </summary>
public static Func<DateTime, CalendarInfo> Monthly
{
get
{
return dt =>
{
var start = dt.AddDays(1 - dt.Day).Date;
var end = Expiry.EndOfMonth(dt);
return new CalendarInfo(start, end - start);
};
}
}
/// <summary>
/// Computes the start of quarter (1st of the starting month of current quarter) of given date/time
/// </summary>
public static Func<DateTime, CalendarInfo> Quarterly
{
get
{
return dt =>
{
var nthQuarter = (dt.Month - 1) / 3;
var firstMonthOfQuarter = nthQuarter * 3 + 1;
var start = new DateTime(dt.Year, firstMonthOfQuarter, 1);
var end = Expiry.EndOfQuarter(dt);
return new CalendarInfo(start, end - start);
};
}
}
/// <summary>
/// Computes the start of year (1st of the current year) of given date/time
/// </summary>
public static Func<DateTime, CalendarInfo> Yearly
{
get
{
return dt =>
{
var start = dt.AddDays(1 - dt.DayOfYear).Date;
var end = Expiry.EndOfYear(dt);
return new CalendarInfo(start, end - start);
};
}
}
}
/// <summary>
/// Calendar Info for storing information related to the start and period of a consolidator
/// </summary>
public readonly struct CalendarInfo
{
/// <summary>
/// Calendar Start
/// </summary>
public DateTime Start { get; init; }
/// <summary>
/// Consolidation Period
/// </summary>
public TimeSpan Period { get; init; }
/// <summary>
/// Calendar End
/// </summary>
public readonly DateTime End => Start + Period;
/// <summary>
/// Constructor for CalendarInfo; used for consolidation calendar
/// </summary>
/// <param name="start">Calendar Start</param>
/// <param name="period">Consolidation Period</param>
public CalendarInfo(DateTime start, TimeSpan period)
{
Start = start;
Period = period;
}
/// <summary>
/// Returns a string containing the Calendar start and the consolidation period
/// </summary>
public override string ToString()
{
return $"{Start} {Period}";
}
/// <summary>
/// Indicates whether the given object is equal to this object, this is, the Calendar start
/// and consolidation period is the same for both
/// </summary>
public override bool Equals(object obj)
{
if (obj is not CalendarInfo other)
{
return false;
}
return Start == other.Start && Period == other.Period;
}
/// <summary>
/// Returns the hash code for this object as an integer
/// </summary>
public override int GetHashCode()
{
unchecked
{
var hashCode = Start.GetHashCode();
return (hashCode * 397) ^ Period.GetHashCode();
}
}
/// <summary>
/// Indicates whether the given object is equal to this object, this is, the Calendar start
/// and consolidation period is the same for both
/// </summary>
public static bool operator ==(CalendarInfo left, CalendarInfo right)
{
return left.Equals(right);
}
/// <summary>
/// Indicates whether the given object is equal to this object, this is, the Calendar start
/// and consolidation period is the same for both
/// </summary>
public static bool operator !=(CalendarInfo left, CalendarInfo right)
{
return !(left == right);
}
}
}
+36
View File
@@ -0,0 +1,36 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
namespace QuantConnect.Data.Consolidators
{
/// <summary>
/// Calendar Type Class; now obsolete routes functions to <see cref="Calendar"/>
/// </summary>
[Obsolete("CalendarType is obsolete, please use Calendar instead")]
public static class CalendarType
{
/// <summary>
/// Computes the start of week (previous Monday) of given date/time
/// </summary>
public static Func<DateTime, CalendarInfo> Weekly => Calendar.Weekly;
/// <summary>
/// Computes the start of month (1st of the current month) of given date/time
/// </summary>
public static Func<DateTime, CalendarInfo> Monthly => Calendar.Monthly;
}
}
@@ -0,0 +1,83 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using QuantConnect.Data.Market;
using Python.Runtime;
namespace QuantConnect.Data.Consolidators
{
/// <summary>
/// This consolidator can transform a stream of <see cref="IBaseData"/> instances into a stream of <see cref="RangeBar"/>.
/// The difference between this consolidator and <see cref="RangeConsolidator"/>, is that this last one creates intermediate/
/// phantom RangeBar's (RangeBar's with zero volume) if the price rises up or falls down by above/below two times the range
/// size. Therefore, <see cref="RangeConsolidator"/> leaves no space between two adyacent RangeBar's since it always start
/// a new RangeBar one range above the last RangeBar's High value or one range below the last RangeBar's Low value, where
/// one range equals to one minimum price change.
/// </summary>
public class ClassicRangeConsolidator : RangeConsolidator
{
/// <summary>
/// Initializes a new instance of the <see cref="ClassicRangeConsolidator" /> class.
/// </summary>
/// <param name="range">The Range interval sets the range in which the price moves, which in turn initiates the formation of a new bar.
/// One range equals to one minimum price change, where this last value is defined depending of the RangeBar's symbol</param>
/// <param name="selector">Extracts the value from a data instance to be formed into a <see cref="RangeBar"/>. The default
/// value is (x => x.Value) the <see cref="IBaseData.Value"/> property on <see cref="IBaseData"/></param>
/// <param name="volumeSelector">Extracts the volume from a data instance. The default value is null which does
/// not aggregate volume per bar, except if the input is a TradeBar.</param>
public ClassicRangeConsolidator(
int range,
Func<IBaseData, decimal> selector = null,
Func<IBaseData, decimal> volumeSelector = null)
: base(range, selector, volumeSelector)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="RangeConsolidator" /> class.
/// </summary>
/// <param name="range">The Range interval sets the range in which the price moves, which in turn initiates the formation of a new bar.
/// One range equals to one minimum price change, where this last value is defined depending of the RangeBar's symbol</param>
/// <param name="selector">Extracts the value from a data instance to be formed into a <see cref="RangeBar"/>. The default
/// value is (x => x.Value) the <see cref="IBaseData.Value"/> property on <see cref="IBaseData"/></param>
/// <param name="volumeSelector">Extracts the volume from a data instance. The default value is null which does
/// not aggregate volume per bar.</param>
public ClassicRangeConsolidator(int range,
PyObject selector,
PyObject volumeSelector = null)
: base(range, selector, volumeSelector)
{
}
/// <summary>
/// Updates the current RangeBar being created with the given data.
/// Additionally, if it's the case, it consolidates the current RangeBar
/// </summary>
/// <param name="time">Time of the given data</param>
/// <param name="currentValue">Value of the given data</param>
/// <param name="volume">Volume of the given data</param>
protected override void UpdateBar(DateTime time, decimal currentValue, decimal volume)
{
CurrentBar.Update(time, currentValue, volume);
if (CurrentBar.IsClosed)
{
OnDataConsolidated(CurrentBar);
CurrentBar = null;
}
}
}
}
@@ -0,0 +1,241 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using Python.Runtime;
using QuantConnect.Data.Market;
namespace QuantConnect.Data.Consolidators
{
/// <summary>
/// This consolidator can transform a stream of <see cref="IBaseData"/> instances into a stream of <see cref="RenkoBar"/>
/// </summary>
public class ClassicRenkoConsolidator : BaseTimelessConsolidator<RenkoBar>
{
private decimal _barSize;
private bool _evenBars;
private decimal? _lastCloseValue;
/// <summary>
/// Bar being created
/// </summary>
protected override RenkoBar CurrentBar { get; set; }
/// <summary>
/// Gets the kind of the bar
/// </summary>
public RenkoType Type => RenkoType.Classic;
/// <summary>
/// Gets a clone of the data being currently consolidated
/// </summary>
public override IBaseData WorkingData => CurrentBar?.Clone();
/// <summary>
/// Gets <see cref="RenkoBar"/> which is the type emitted in the <see cref="IDataConsolidator.DataConsolidated"/> event.
/// </summary>
public override Type OutputType => typeof(RenkoBar);
/// <summary>
/// Initializes a new instance of the <see cref="ClassicRenkoConsolidator"/> class using the specified <paramref name="barSize"/>.
/// The value selector will by default select <see cref="IBaseData.Value"/>
/// The volume selector will by default select zero.
/// </summary>
/// <param name="barSize">The constant value size of each bar</param>
/// <param name="evenBars">When true bar open/close will be a multiple of the barSize</param>
public ClassicRenkoConsolidator(decimal barSize, bool evenBars = true)
: base()
{
EpsilonCheck(barSize);
_barSize = barSize;
_evenBars = evenBars;
}
/// <summary>
/// Initializes a new instance of the <see cref="ClassicRenkoConsolidator" /> class.
/// </summary>
/// <param name="barSize">The size of each bar in units of the value produced by <paramref name="selector"/></param>
/// <param name="selector">Extracts the value from a data instance to be formed into a <see cref="RenkoBar"/>. The default
/// value is (x => x.Value) the <see cref="IBaseData.Value"/> property on <see cref="IBaseData"/></param>
/// <param name="volumeSelector">Extracts the volume from a data instance. The default value is null which does
/// not aggregate volume per bar.</param>
/// <param name="evenBars">When true bar open/close will be a multiple of the barSize</param>
public ClassicRenkoConsolidator(
decimal barSize,
Func<IBaseData, decimal> selector,
Func<IBaseData, decimal> volumeSelector = null,
bool evenBars = true)
: base(selector, volumeSelector)
{
EpsilonCheck(barSize);
_barSize = barSize;
_evenBars = evenBars;
}
/// <summary>
///Initializes a new instance of the <see cref="ClassicRenkoConsolidator" /> class.
/// </summary>
/// <param name="barSize">The constant value size of each bar</param>
/// <param name="type">The RenkoType of the bar</param>
[Obsolete("Please use the new RenkoConsolidator if RenkoType is not Classic")]
public ClassicRenkoConsolidator(decimal barSize, RenkoType type)
: this(barSize, true)
{
if (type != RenkoType.Classic)
{
throw new ArgumentException("Please use the new RenkoConsolidator type if RenkoType is not Classic");
}
}
/// <summary>
/// Initializes a new instance of the <see cref="ClassicRenkoConsolidator" /> class.
/// </summary>
/// <param name="barSize">The size of each bar in units of the value produced by <paramref name="selector"/></param>
/// <param name="selector">Extracts the value from a data instance to be formed into a <see cref="RenkoBar"/>. The default
/// value is (x => x.Value) the <see cref="IBaseData.Value"/> property on <see cref="IBaseData"/></param>
/// <param name="volumeSelector">Extracts the volume from a data instance. The default value is null which does
/// not aggregate volume per bar.</param>
/// <param name="evenBars">When true bar open/close will be a multiple of the barSize</param>
public ClassicRenkoConsolidator(decimal barSize,
PyObject selector,
PyObject volumeSelector = null,
bool evenBars = true)
: base(selector, volumeSelector)
{
EpsilonCheck(barSize);
_barSize = barSize;
_evenBars = evenBars;
}
/// <summary>
/// Resets the ClassicRenkoConsolidator
/// </summary>
public override void Reset()
{
base.Reset();
_lastCloseValue = null;
}
/// <summary>
/// Updates the current RangeBar being created with the given data.
/// Additionally, if it's the case, it consolidates the current RangeBar
/// </summary>
/// <param name="time">Time of the given data</param>
/// <param name="currentValue">Value of the given data</param>
/// <param name="volume">Volume of the given data</param>
protected override void UpdateBar(DateTime time, decimal currentValue, decimal volume)
{
CurrentBar.Update(time, currentValue, volume);
if (CurrentBar.IsClosed)
{
_lastCloseValue = CurrentBar.Close;
OnDataConsolidated(CurrentBar);
CurrentBar = null;
}
}
/// <summary>
/// Creates a new bar with the given data
/// </summary>
/// <param name="data">The new data for the bar</param>
/// <param name="currentValue">The new value for the bar</param>
/// <param name="volume">The new volume to the bar</param>
protected override void CreateNewBar(IBaseData data, decimal currentValue, decimal volume)
{
var open = _lastCloseValue ?? currentValue;
if (_evenBars && !_lastCloseValue.HasValue)
{
open = Math.Ceiling(open / _barSize) * _barSize;
}
CurrentBar = new RenkoBar(data.Symbol, data.Time, _barSize, open, volume);
}
private static void EpsilonCheck(decimal barSize)
{
if (barSize < Extensions.GetDecimalEpsilon())
{
throw new ArgumentOutOfRangeException(nameof(barSize),
"RenkoConsolidator bar size must be positve and greater than 1e-28");
}
}
}
/// <summary>
/// Provides a type safe wrapper on the RenkoConsolidator class. This just allows us to define our selector functions with the real type they'll be receiving
/// </summary>
/// <typeparam name="TInput"></typeparam>
public class ClassicRenkoConsolidator<TInput> : ClassicRenkoConsolidator
where TInput : IBaseData
{
/// <summary>
/// Initializes a new instance of the <see cref="ClassicRenkoConsolidator" /> class.
/// </summary>
/// <param name="barSize">The size of each bar in units of the value produced by <paramref name="selector"/></param>
/// <param name="selector">Extracts the value from a data instance to be formed into a <see cref="RenkoBar"/>. The default
/// value is (x => x.Value) the <see cref="IBaseData.Value"/> property on <see cref="IBaseData"/></param>
/// <param name="volumeSelector">Extracts the volume from a data instance. The default value is null which does
/// not aggregate volume per bar.</param>
/// <param name="evenBars">When true bar open/close will be a multiple of the barSize</param>
public ClassicRenkoConsolidator(
decimal barSize,
Func<TInput, decimal> selector,
Func<TInput, decimal> volumeSelector = null,
bool evenBars = true
)
: base(barSize, x => selector((TInput) x),
volumeSelector == null ? (Func<IBaseData, decimal>) null : x => volumeSelector((TInput) x), evenBars)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ClassicRenkoConsolidator"/> class using the specified <paramref name="barSize"/>.
/// The value selector will by default select <see cref="IBaseData.Value"/>
/// The volume selector will by default select zero.
/// </summary>
/// <param name="barSize">The constant value size of each bar</param>
/// <param name="evenBars">When true bar open/close will be a multiple of the barSize</param>
public ClassicRenkoConsolidator(decimal barSize, bool evenBars = true)
: this(barSize, x => x.Value, x => 0, evenBars)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ClassicRenkoConsolidator"/> class using the specified <paramref name="barSize"/>.
/// The value selector will by default select <see cref="IBaseData.Value"/>
/// The volume selector will by default select zero.
/// </summary>
/// <param name="barSize">The constant value size of each bar</param>
/// <param name="type">The RenkoType of the bar</param>
[Obsolete("Please use the WickedRenkoConsolidator if RenkoType is not Classic")]
public ClassicRenkoConsolidator(decimal barSize, RenkoType type)
: base(barSize, type)
{
}
/// <summary>
/// Updates this consolidator with the specified data.
/// </summary>
/// <remarks>
/// Type safe shim method.
/// </remarks>
/// <param name="data">The new data for the consolidator</param>
public void Update(TInput data)
{
base.Update(data);
}
}
}
@@ -0,0 +1,132 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using QuantConnect.Indicators;
namespace QuantConnect.Data.Consolidators
{
/// <summary>
/// Provides a base implementation for consolidators, including a built-in rolling window
/// that stores the history of consolidated bars.
/// </summary>
public abstract class ConsolidatorBase : WindowBase<IBaseData>, IDataConsolidator
{
private IBaseData _consolidated;
/// <summary>
/// Gets the most recently consolidated piece of data. This will be null if this consolidator
/// has not produced any data yet.
/// </summary>
public IBaseData Consolidated
{
get
{
return _consolidated;
}
protected set
{
_consolidated = value;
}
}
/// <summary>
/// Gets a clone of the data being currently consolidated
/// </summary>
public abstract IBaseData WorkingData { get; }
/// <summary>
/// Gets the type consumed by this consolidator
/// </summary>
public abstract Type InputType { get; }
/// <summary>
/// Gets the type produced by this consolidator
/// </summary>
public abstract Type OutputType { get; }
/// <summary>
/// Updates this consolidator with the specified data
/// </summary>
/// <param name="data">The new data for the consolidator</param>
public abstract void Update(IBaseData data);
/// <summary>
/// Scans this consolidator to see if it should emit a bar due to time passing
/// </summary>
/// <param name="currentLocalTime">The current time in the local time zone (same as <see cref="BaseData.Time"/>)</param>
public abstract void Scan(DateTime currentLocalTime);
/// <summary>
/// Event handler that fires when a new piece of data is produced. This is the single subscription
/// point, shared by the <see cref="IDataConsolidator"/> interface and by derived consolidators whose
/// output is a base data bar, so subscribing and unsubscribing always target the same handler list.
/// </summary>
public event DataConsolidatedHandler DataConsolidated;
/// <summary>
/// Event invocator for the DataConsolidated event. Populates the rolling window, raises the
/// strongly typed and interface events, and finally updates the <see cref="Consolidated"/> property.
/// </summary>
protected virtual void OnDataConsolidated(IBaseData consolidated)
{
// populate the rolling window before firing any event so that, inside a DataConsolidated
// handler, consolidator[0] is the bar that was just produced. Skip null bars, an out of order
// data point can produce a null bar in count mode, so we never push null nor wipe the history
if (consolidated != null)
{
Current = consolidated;
}
// let derived consolidators raise their strongly typed DataConsolidated event
FireDataConsolidated(consolidated);
DataConsolidated?.Invoke(this, consolidated);
// assign the Consolidated property after the event handlers are fired,
// this allows the event handlers to look at the new consolidated data
// and the previous consolidated data at the same time without extra bookkeeping
Consolidated = consolidated;
}
/// <summary>
/// Raises the strongly typed DataConsolidated event exposed by derived consolidators that produce a
/// more specific bar type. Invoked after the rolling window is populated and before the shared event
/// so every handler sees the same window. Consolidators whose output is a base data bar do not need
/// to override this, the shared <see cref="DataConsolidated"/> event already carries their bar.
/// </summary>
/// <param name="consolidated">The newly consolidated data</param>
protected virtual void FireDataConsolidated(IBaseData consolidated)
{
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public virtual void Dispose()
{
DataConsolidated = null;
}
/// <summary>
/// Resets this consolidator, clearing consolidated data and the rolling window.
/// </summary>
public virtual void Reset()
{
Consolidated = null;
ResetWindow();
}
}
}
@@ -0,0 +1,73 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
using System;
namespace QuantConnect.Data.Consolidators
{
/// <summary>
/// Represents a type that consumes BaseData instances and fires an event with consolidated
/// and/or aggregated data.
/// </summary>
/// <typeparam name="TInput">The type consumed by the consolidator</typeparam>
public abstract class DataConsolidator<TInput> : ConsolidatorBase
where TInput : IBaseData
{
/// <summary>
/// Updates this consolidator with the specified data
/// </summary>
/// <param name="data">The new data for the consolidator</param>
public override void Update(IBaseData data)
{
if (!(data is TInput))
{
throw new ArgumentNullException(nameof(data),
$"Received type of {data.GetType().Name} but expected {typeof(TInput).Name}"
);
}
Update((TInput)data);
}
/// <summary>
/// Scans this consolidator to see if it should emit a bar due to time passing
/// </summary>
/// <param name="currentLocalTime">The current time in the local time zone (same as <see cref="BaseData.Time"/>)</param>
public abstract override void Scan(DateTime currentLocalTime);
/// <summary>
/// Gets a clone of the data being currently consolidated
/// </summary>
public abstract override IBaseData WorkingData { get; }
/// <summary>
/// Gets the type consumed by this consolidator
/// </summary>
public override Type InputType => typeof(TInput);
/// <summary>
/// Gets the type produced by this consolidator
/// </summary>
public abstract override Type OutputType { get; }
/// <summary>
/// Updates this consolidator with the specified data. This method is
/// responsible for raising the DataConsolidated event
/// </summary>
/// <param name="data">The new data for the consolidator</param>
public abstract void Update(TInput data);
}
}
@@ -0,0 +1,48 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using QuantConnect.Data.Market;
using QuantConnect.Data.Consolidators;
using QuantConnect.Data;
namespace Common.Data.Consolidators
{
/// <summary>
/// This consolidator transforms a stream of <see cref="BaseData"/> instances into a stream of <see cref="RenkoBar"/>
/// with a constant dollar volume for each bar.
/// </summary>
public class DollarVolumeRenkoConsolidator : VolumeRenkoConsolidator
{
/// <summary>
/// Initializes a new instance of the <see cref="DollarVolumeRenkoConsolidator"/> class using the specified <paramref name="barSize"/>.
/// </summary>
/// <param name="barSize">The constant dollar volume size of each bar</param>
public DollarVolumeRenkoConsolidator(decimal barSize)
: base(barSize)
{
}
/// <summary>
/// Converts raw volume into dollar volume by multiplying it with the trade price.
/// </summary>
/// <param name="volume">The raw trade volume</param>
/// <param name="price">The trade price</param>
/// <returns>The dollar volume</returns>
protected override decimal AdjustVolume(decimal volume, decimal price)
{
return volume * price;
}
}
}
@@ -0,0 +1,115 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using QuantConnect.Data.Market;
namespace QuantConnect.Data.Consolidators
{
/// <summary>
/// A data csolidator that can make trade bars from DynamicData derived types. This is useful for
/// aggregating Quandl and other highly flexible dynamic custom data types.
/// </summary>
public class DynamicDataConsolidator : TradeBarConsolidatorBase<DynamicData>
{
/// <summary>
/// Creates a consolidator to produce a new 'TradeBar' representing the period.
/// </summary>
/// <param name="period">The minimum span of time before emitting a consolidated bar</param>
public DynamicDataConsolidator(TimeSpan period)
: base(period)
{
}
/// <summary>
/// Creates a consolidator to produce a new 'TradeBar' representing the last count pieces of data.
/// </summary>
/// <param name="maxCount">The number of pieces to accept before emiting a consolidated bar</param>
public DynamicDataConsolidator(int maxCount)
: base(maxCount)
{
}
/// <summary>
/// Creates a consolidator to produce a new 'TradeBar' representing the last count pieces of data or the period, whichever comes first.
/// </summary>
/// <param name="maxCount">The number of pieces to accept before emiting a consolidated bar</param>
/// <param name="period">The minimum span of time before emitting a consolidated bar</param>
public DynamicDataConsolidator(int maxCount, TimeSpan period)
: base(maxCount, period)
{
}
/// <summary>
/// Creates a consolidator to produce a new 'TradeBar' representing the last count pieces of data or the period, whichever comes first.
/// </summary>
/// <param name="func">Func that defines the start time of a consolidated data</param>
public DynamicDataConsolidator(Func<DateTime, CalendarInfo> func)
: base(func)
{
}
/// <summary>
/// Aggregates the new 'data' into the 'workingBar'. The 'workingBar' will be
/// null following the event firing
/// </summary>
/// <param name="workingBar">The bar we're building, null if the event was just fired and we're starting a new trade bar</param>
/// <param name="data">The new data</param>
protected override void AggregateBar(ref TradeBar workingBar, DynamicData data)
{
// grab the properties, if they don't exist just use the .Value property
var open = GetNamedPropertyOrValueProperty(data, "Open");
var high = GetNamedPropertyOrValueProperty(data, "High");
var low = GetNamedPropertyOrValueProperty(data, "Low");
var close = GetNamedPropertyOrValueProperty(data, "Close");
// if we have volume, use it, otherwise just use zero
var volume = data.HasProperty("Volume")
? data.GetProperty("Volume").ConvertInvariant<long>()
: 0L;
if (workingBar == null)
{
workingBar = new TradeBar
{
Symbol = data.Symbol,
Time = GetRoundedBarTime(data),
Open = open,
High = high,
Low = low,
Close = close,
Volume = volume
};
}
else
{
//Aggregate the working bar
workingBar.Close = close;
workingBar.Volume += volume;
if (low < workingBar.Low) workingBar.Low = low;
if (high > workingBar.High) workingBar.High = high;
}
}
private static decimal GetNamedPropertyOrValueProperty(DynamicData data, string propertyName)
{
if (!data.HasProperty(propertyName))
{
return data.Value;
}
return data.GetProperty(propertyName).ConvertInvariant<decimal>();
}
}
}
@@ -0,0 +1,70 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using QuantConnect.Data.Market;
namespace QuantConnect.Data.Consolidators
{
/// <summary>
/// Provides an implementation of <see cref="IDataConsolidator"/> that preserve the input
/// data unmodified. The input data is filtering by the specified predicate function
/// </summary>
/// <typeparam name="T">The type of data</typeparam>
public class FilteredIdentityDataConsolidator<T> : IdentityDataConsolidator<T>
where T : IBaseData
{
private readonly Func<T, bool> _predicate;
/// <summary>
/// Initializes a new instance of the <see cref="FilteredIdentityDataConsolidator{T}"/> class
/// </summary>
/// <param name="predicate">The predicate function, returning true to accept data and false to reject data</param>
public FilteredIdentityDataConsolidator(Func<T, bool> predicate)
{
this._predicate = predicate;
}
/// <summary>
/// Updates this consolidator with the specified data
/// </summary>
/// <param name="data">The new data for the consolidator</param>
public override void Update(T data)
{
// only permit data that passes our predicate function to be passed through
if (_predicate(data))
{
base.Update(data);
}
}
}
/// <summary>
/// Provides factory methods for creating instances of <see cref="FilteredIdentityDataConsolidator{T}"/>
/// </summary>
public static class FilteredIdentityDataConsolidator
{
/// <summary>
/// Creates a new instance of <see cref="FilteredIdentityDataConsolidator{T}"/> that filters ticks
/// based on the specified <see cref="TickType"/>
/// </summary>
/// <param name="tickType">The tick type of data to accept</param>
/// <returns>A new <see cref="FilteredIdentityDataConsolidator{T}"/> that filters based on the provided tick type</returns>
public static FilteredIdentityDataConsolidator<Tick> ForTickType(TickType tickType)
{
return new FilteredIdentityDataConsolidator<Tick>(tick => tick.TickType == tickType);
}
}
}
@@ -0,0 +1,78 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
namespace QuantConnect.Data.Consolidators
{
/// <summary>
/// Event handler type for the IDataConsolidator.DataConsolidated event
/// </summary>
/// <param name="sender">The consolidator that fired the event</param>
/// <param name="consolidated">The consolidated piece of data</param>
public delegate void DataConsolidatedHandler(object sender, IBaseData consolidated);
/// <summary>
/// Represents a type capable of taking BaseData updates and firing events containing new
/// 'consolidated' data. These types can be used to produce larger bars, or even be used to
/// transform the data before being sent to another component. The most common usage of these
/// types is with indicators.
/// </summary>
public interface IDataConsolidator : IDisposable
{
/// <summary>
/// Gets the most recently consolidated piece of data. This will be null if this consolidator
/// has not produced any data yet.
/// </summary>
IBaseData Consolidated { get; }
/// <summary>
/// Gets a clone of the data being currently consolidated
/// </summary>
IBaseData WorkingData { get; }
/// <summary>
/// Gets the type consumed by this consolidator
/// </summary>
Type InputType { get; }
/// <summary>
/// Gets the type produced by this consolidator
/// </summary>
Type OutputType { get; }
/// <summary>
/// Updates this consolidator with the specified data
/// </summary>
/// <param name="data">The new data for the consolidator</param>
void Update(IBaseData data);
/// <summary>
/// Scans this consolidator to see if it should emit a bar due to time passing
/// </summary>
/// <param name="currentLocalTime">The current time in the local time zone (same as <see cref="BaseData.Time"/>)</param>
void Scan(DateTime currentLocalTime);
/// <summary>
/// Resets the consolidator
/// </summary>
void Reset();
/// <summary>
/// Event handler that fires when a new piece of data is produced
/// </summary>
event DataConsolidatedHandler DataConsolidated;
}
}
@@ -0,0 +1,85 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using QuantConnect.Data.Market;
namespace QuantConnect.Data.Consolidators
{
/// <summary>
/// Represents the simplest DataConsolidator implementation, one that is defined
/// by a straight pass through of the data. No projection or aggregation is performed.
/// </summary>
/// <typeparam name="T">The type of data</typeparam>
public class IdentityDataConsolidator<T> : DataConsolidator<T>
where T : IBaseData
{
private static readonly bool IsTick = typeof(T) == typeof(Tick);
private T _last;
/// <summary>
/// Stores the timestamp of the last processed data item.
/// </summary>
private DateTime _lastTime;
/// <summary>
/// Gets a clone of the data being currently consolidated
/// </summary>
public override IBaseData WorkingData
{
get { return _last == null ? null : _last.Clone(); }
}
/// <summary>
/// Gets the type produced by this consolidator
/// </summary>
public override Type OutputType
{
get { return typeof(T); }
}
/// <summary>
/// Updates this consolidator with the specified data
/// </summary>
/// <param name="data">The new data for the consolidator</param>
public override void Update(T data)
{
if (IsTick || _last == null || data.EndTime != _lastTime)
{
OnDataConsolidated(data);
_last = data;
_lastTime = data.EndTime;
}
}
/// <summary>
/// Scans this consolidator to see if it should emit a bar due to time passing
/// </summary>
/// <param name="currentLocalTime">The current time in the local time zone (same as <see cref="BaseData.Time"/>)</param>
public override void Scan(DateTime currentLocalTime)
{
}
/// <summary>
/// Resets the consolidator
/// </summary>
public override void Reset()
{
base.Reset();
_last = default(T);
}
}
}
@@ -0,0 +1,278 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2024 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using NodaTime;
using QuantConnect.Util;
using QuantConnect.Securities;
using QuantConnect.Data.Market;
namespace QuantConnect.Data.Consolidators
{
/// <summary>
/// Consolidator for open markets bar only, extended hours bar are not consolidated.
/// </summary>
public class MarketHourAwareConsolidator : ConsolidatorBase
{
private readonly bool _dailyStrictEndTimeEnabled;
private readonly bool _extendedMarketHours;
private bool _useStrictEndTime;
/// <summary>
/// The consolidation period requested
/// </summary>
protected TimeSpan Period { get; }
/// <summary>
/// The consolidator instance
/// </summary>
protected IDataConsolidator Consolidator { get; }
/// <summary>
/// The associated security exchange hours instance
/// </summary>
protected SecurityExchangeHours ExchangeHours { get; set; }
/// <summary>
/// The associated data time zone
/// </summary>
protected DateTimeZone DataTimeZone { get; set; }
/// <summary>
/// Gets the type consumed by this consolidator
/// </summary>
public override Type InputType => Consolidator.InputType;
/// <summary>
/// Gets a clone of the data being currently consolidated
/// </summary>
public override IBaseData WorkingData => Consolidator.WorkingData;
/// <summary>
/// Gets the type produced by this consolidator
/// </summary>
public override Type OutputType => Consolidator.OutputType;
/// <summary>
/// Initializes a new instance of the <see cref="MarketHourAwareConsolidator"/> class.
/// </summary>
/// <param name="resolution">The resolution.</param>
/// <param name="dataType">The target data type</param>
/// <param name="tickType">The target tick type</param>
/// <param name="extendedMarketHours">True if extended market hours should be consolidated</param>
public MarketHourAwareConsolidator(bool dailyStrictEndTimeEnabled, Resolution resolution, Type dataType, TickType tickType, bool extendedMarketHours)
{
_dailyStrictEndTimeEnabled = dailyStrictEndTimeEnabled;
Period = resolution.ToTimeSpan();
_extendedMarketHours = extendedMarketHours;
Consolidator = CreateConsolidator(resolution, dataType, tickType);
Consolidator.DataConsolidated += ForwardConsolidatedBar;
}
/// <summary>
/// Initializes a new instance of the <see cref="MarketHourAwareConsolidator"/> class for an arbitrary period.
/// Intraday periods are anchored to the market open without extending past the close.
/// </summary>
/// <param name="dailyStrictEndTimeEnabled">True if daily strict end times should be enabled</param>
/// <param name="period">The consolidation period</param>
/// <param name="dataType">The target data type</param>
/// <param name="tickType">The target tick type</param>
/// <param name="extendedMarketHours">True if extended market hours should be consolidated</param>
public MarketHourAwareConsolidator(bool dailyStrictEndTimeEnabled, TimeSpan period, Type dataType, TickType tickType, bool extendedMarketHours)
{
_dailyStrictEndTimeEnabled = dailyStrictEndTimeEnabled;
Period = period;
_extendedMarketHours = extendedMarketHours;
// when the period exactly matches a standard resolution, reuse the resolution based consolidation so its
// well-tested behavior is preserved; only arbitrary periods need the market-open anchored intraday calendar
var resolution = period.ToHigherResolutionEquivalent(false);
if (resolution.ToTimeSpan() == period)
{
Consolidator = CreateConsolidator(resolution, dataType, tickType);
}
else
{
Func<DateTime, CalendarInfo> calendar = period < Time.OneDay ? IntradayCalendar : DailyStrictEndTime;
Consolidator = CreateConsolidator(calendar, dataType, tickType);
}
Consolidator.DataConsolidated += ForwardConsolidatedBar;
}
/// <summary>
/// Creates the inner consolidator that produces the requested <paramref name="dataType"/> output.
/// </summary>
protected virtual IDataConsolidator CreateConsolidator(Resolution resolution, Type dataType, TickType tickType)
{
if (dataType == typeof(Tick))
{
if (tickType == TickType.Trade)
{
return resolution == Resolution.Daily
? new TickConsolidator(DailyStrictEndTime)
: new TickConsolidator(Period);
}
return resolution == Resolution.Daily
? new TickQuoteBarConsolidator(DailyStrictEndTime)
: new TickQuoteBarConsolidator(Period);
}
if (dataType == typeof(TradeBar))
{
return resolution == Resolution.Daily
? new TradeBarConsolidator(DailyStrictEndTime)
: new TradeBarConsolidator(Period);
}
if (dataType == typeof(QuoteBar))
{
return resolution == Resolution.Daily
? new QuoteBarConsolidator(DailyStrictEndTime)
: new QuoteBarConsolidator(Period);
}
throw new ArgumentNullException(nameof(dataType), $"{dataType.Name} not supported");
}
/// <summary>
/// Creates the underlying calendar based consolidator for the given data type, used for arbitrary periods
/// </summary>
protected virtual IDataConsolidator CreateConsolidator(Func<DateTime, CalendarInfo> calendar, Type dataType, TickType tickType)
{
if (dataType == typeof(Tick))
{
return tickType == TickType.Trade
? new TickConsolidator(calendar)
: new TickQuoteBarConsolidator(calendar);
}
if (dataType == typeof(TradeBar))
{
return new TradeBarConsolidator(calendar);
}
if (dataType == typeof(QuoteBar))
{
return new QuoteBarConsolidator(calendar);
}
throw new ArgumentNullException(nameof(dataType), $"{dataType.Name} not supported");
}
/// <summary>
/// Updates this consolidator with the specified data
/// </summary>
/// <param name="data">The new data for the consolidator</param>
public override void Update(IBaseData data)
{
Initialize(data);
// US equity hour data from the database starts at 9am but the exchange opens at 9:30am. Thus, we need to handle
// this case specifically to avoid skipping the first hourly bar. To avoid this, we assert the period is daily,
// the data resolution is hour and the exchange opens at any point in time over the data.Time to data.EndTime interval
if (_extendedMarketHours ||
ExchangeHours.IsOpen(data.Time, false) ||
(Period == Time.OneDay && (data.EndTime - data.Time >= Time.OneHour) && ExchangeHours.IsOpen(data.Time, data.EndTime, false)))
{
Consolidator.Update(data);
}
}
/// <summary>
/// Scans this consolidator to see if it should emit a bar due to time passing
/// </summary>
/// <param name="currentLocalTime">The current time in the local time zone (same as <see cref="P:QuantConnect.Data.BaseData.Time" />)</param>
public override void Scan(DateTime currentLocalTime)
{
Consolidator.Scan(currentLocalTime);
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public override void Dispose()
{
Consolidator.DataConsolidated -= ForwardConsolidatedBar;
Consolidator.Dispose();
base.Dispose();
}
/// <summary>
/// Resets the consolidator
/// </summary>
public override void Reset()
{
_useStrictEndTime = false;
ExchangeHours = null;
DataTimeZone = null;
Consolidator.Reset();
base.Reset();
}
/// <summary>
/// Perform late initialization based on the datas symbol
/// </summary>
protected void Initialize(IBaseData data)
{
if (ExchangeHours == null)
{
var symbol = data.Symbol;
var marketHoursDatabase = MarketHoursDatabase.FromDataFolder();
ExchangeHours = marketHoursDatabase.GetExchangeHours(symbol.ID.Market, symbol, symbol.SecurityType);
DataTimeZone = marketHoursDatabase.GetDataTimeZone(symbol.ID.Market, symbol, symbol.SecurityType);
_useStrictEndTime = UseStrictEndTime(data.Symbol);
}
}
/// <summary>
/// Determines a bar start time and period
/// </summary>
protected virtual CalendarInfo DailyStrictEndTime(DateTime dateTime)
{
// strict end times describe a single daily bar, so periods larger than a day fall back to standard period consolidation
if (!_useStrictEndTime || Period > Time.OneDay)
{
return new(Period > Time.OneDay ? dateTime : dateTime.RoundDown(Period), Period);
}
return LeanData.GetDailyCalendar(dateTime, ExchangeHours, _extendedMarketHours);
}
/// <summary>
/// Determines a bar start time and period for intraday consolidation, anchored to the market open
/// without extending past the market close so a bar never spans across closed market hours
/// </summary>
protected virtual CalendarInfo IntradayCalendar(DateTime dateTime)
{
if (ExchangeHours == null || ExchangeHours.IsMarketAlwaysOpen)
{
return new(dateTime.RoundDown(Period), Period);
}
return LeanData.GetIntradayCalendar(dateTime, Period, ExchangeHours, _extendedMarketHours);
}
/// <summary>
/// Useful for testing
/// </summary>
protected virtual bool UseStrictEndTime(Symbol symbol)
{
return LeanData.UseStrictEndTime(_dailyStrictEndTimeEnabled, symbol, Period, ExchangeHours);
}
/// <summary>
/// Will forward the underlying consolidated bar to consumers on this object.
/// This wrapper keeps its own rolling window in addition to the inner consolidator's window.
/// </summary>
protected virtual void ForwardConsolidatedBar(object sender, IBaseData consolidated)
{
OnDataConsolidated(consolidated);
}
}
}
@@ -0,0 +1,155 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using QuantConnect.Data.Market;
using Python.Runtime;
namespace QuantConnect.Data.Consolidators
{
/// <summary>
/// Type capable of consolidating open interest
/// </summary>
public class OpenInterestConsolidator : PeriodCountConsolidatorBase<Tick, OpenInterest>
{
private bool _hourOrDailyConsolidation;
// Keep track of the last input to detect hour or date change
private Tick _lastInput;
/// <summary>
/// Create a new OpenInterestConsolidator for the desired resolution
/// </summary>
/// <param name="resolution">The resolution desired</param>
/// <returns>A consolidator that produces data on the resolution interval</returns>
public static OpenInterestConsolidator FromResolution(Resolution resolution)
{
return new OpenInterestConsolidator(resolution.ToTimeSpan());
}
/// <summary>
/// Creates a consolidator to produce a new 'OpenInterest' representing the period
/// </summary>
/// <param name="period">The minimum span of time before emitting a consolidated bar</param>
/// <param name="startTime">Optionally the bar start time anchor to use</param>
public OpenInterestConsolidator(TimeSpan period, TimeSpan? startTime = null)
: base(period, startTime)
{
_hourOrDailyConsolidation = period >= Time.OneHour;
}
/// <summary>
/// Creates a consolidator to produce a new 'OpenInterest' representing the last count pieces of data
/// </summary>
/// <param name="maxCount">The number of pieces to accept before emitting a consolidated bar</param>
public OpenInterestConsolidator(int maxCount)
: base(maxCount)
{
}
/// <summary>
/// Creates a consolidator to produce a new 'OpenInterest' representing the last count pieces of data or the period, whichever comes first
/// </summary>
/// <param name="maxCount">The number of pieces to accept before emitting a consolidated bar</param>
/// <param name="period">The minimum span of time before emitting a consolidated bar</param>
public OpenInterestConsolidator(int maxCount, TimeSpan period)
: base(maxCount, period)
{
}
/// <summary>
/// Creates a consolidator to produce a new 'OpenInterest'
/// </summary>
/// <param name="func">Func that defines the start time of a consolidated data</param>
public OpenInterestConsolidator(Func<DateTime, CalendarInfo> func)
: base(func)
{
}
/// <summary>
/// Creates a consolidator to produce a new 'OpenInterest'
/// </summary>
/// <param name="pyfuncobj">Python function object that defines the start time of a consolidated data</param>
public OpenInterestConsolidator(PyObject pyfuncobj)
: base(pyfuncobj)
{
}
/// <summary>
/// Determines whether or not the specified data should be processed
/// </summary>
/// <param name="data">The data to check</param>
/// <returns>True if the consolidator should process this data, false otherwise</returns>
protected override bool ShouldProcess(Tick data)
{
return data.TickType == TickType.OpenInterest;
}
/// <summary>
/// Aggregates the new 'data' into the 'workingBar'. The 'workingBar' will be
/// null following the event firing
/// </summary>
/// <param name="workingBar">The bar we're building, null if the event was just fired and we're starting a new OI bar</param>
/// <param name="data">The new data</param>
protected override void AggregateBar(ref OpenInterest workingBar, Tick data)
{
if (workingBar == null)
{
workingBar = new OpenInterest
{
Symbol = data.Symbol,
Time = _hourOrDailyConsolidation ? data.EndTime : GetRoundedBarTime(data),
Value = data.Value
};
}
else
{
//Update the working bar
workingBar.Value = data.Value;
// If we are consolidating hourly or daily, we need to update the time of the working bar
// for the end time to match the last data point time
if (_hourOrDailyConsolidation)
{
workingBar.Time = data.EndTime;
}
}
}
/// <summary>
/// Updates this consolidator with the specified data. This method is
/// responsible for raising the DataConsolidated event.
/// It will check for date or hour change and force consolidation if needed.
/// </summary>
/// <param name="data">The new data for the consolidator</param>
public override void Update(Tick data)
{
if (_lastInput != null &&
_hourOrDailyConsolidation &&
// Detect hour or date change
((Period == Time.OneHour && data.EndTime.Hour != _lastInput.EndTime.Hour) ||
(Period == Time.OneDay && data.EndTime.Date != _lastInput.EndTime.Date)))
{
// Date or hour change, force consolidation, no need to wait for the whole period to pass.
// Force consolidation by scanning at a time after the end of the period
Scan(_lastInput.EndTime.Add(Period.Value + Time.OneSecond));
}
base.Update(data);
_lastInput = data;
}
}
}
@@ -0,0 +1,474 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
using System;
using Python.Runtime;
using QuantConnect.Util;
using QuantConnect.Data.Market;
using System.Runtime.CompilerServices;
namespace QuantConnect.Data.Consolidators
{
/// <summary>
/// Provides a base class for consolidators that emit data based on the passing of a period of time
/// or after seeing a max count of data points.
/// </summary>
/// <typeparam name="T">The input type of the consolidator</typeparam>
/// <typeparam name="TConsolidated">The output type of the consolidator</typeparam>
public abstract class PeriodCountConsolidatorBase<T, TConsolidated> : DataConsolidator<T>
where T : IBaseData
where TConsolidated : BaseData
{
// The SecurityIdentifier that we are consolidating for.
private SecurityIdentifier _securityIdentifier;
private bool _securityIdentifierIsSet;
//The number of data updates between creating new bars.
private int? _maxCount;
//
private IPeriodSpecification _periodSpecification;
//The minimum timespan between creating new bars.
private TimeSpan? _period;
//The number of pieces of data we've accumulated since our last emit
private int _currentCount;
//The working bar used for aggregating the data
protected TConsolidated _workingBar;
//The last time we emitted a consolidated bar
private DateTime? _lastEmit;
private bool _validateTimeSpan;
private PeriodCountConsolidatorBase(IPeriodSpecification periodSpecification)
{
_periodSpecification = periodSpecification;
_period = _periodSpecification.Period;
}
/// <summary>
/// Creates a consolidator to produce a new <typeparamref name="TConsolidated"/> instance representing the period
/// </summary>
/// <param name="period">The minimum span of time before emitting a consolidated bar</param>
/// <param name="startTime">Optionally the bar start time anchor to use</param>
protected PeriodCountConsolidatorBase(TimeSpan period, TimeSpan? startTime = null)
: this(new TimeSpanPeriodSpecification(period, startTime))
{
_period = _periodSpecification.Period;
}
/// <summary>
/// Creates a consolidator to produce a new <typeparamref name="TConsolidated"/> instance representing the last count pieces of data
/// </summary>
/// <param name="maxCount">The number of pieces to accept before emiting a consolidated bar</param>
protected PeriodCountConsolidatorBase(int maxCount)
: this(new BarCountPeriodSpecification())
{
_maxCount = maxCount;
}
/// <summary>
/// Creates a consolidator to produce a new <typeparamref name="TConsolidated"/> instance representing the last count pieces of data or the period, whichever comes first
/// </summary>
/// <param name="maxCount">The number of pieces to accept before emiting a consolidated bar</param>
/// <param name="period">The minimum span of time before emitting a consolidated bar</param>
protected PeriodCountConsolidatorBase(int maxCount, TimeSpan period)
: this(new MixedModePeriodSpecification(period))
{
_maxCount = maxCount;
_period = _periodSpecification.Period;
}
/// <summary>
/// Creates a consolidator to produce a new <typeparamref name="TConsolidated"/> instance representing the last count pieces of data or the period, whichever comes first
/// </summary>
/// <param name="func">Func that defines the start time of a consolidated data</param>
protected PeriodCountConsolidatorBase(Func<DateTime, CalendarInfo> func)
: this(new FuncPeriodSpecification(func))
{
_period = Time.OneSecond;
}
/// <summary>
/// Creates a consolidator to produce a new <typeparamref name="TConsolidated"/> instance representing the last count pieces of data or the period, whichever comes first
/// </summary>
/// <param name="pyObject">Python object that defines either a function object that defines the start time of a consolidated data or a timespan</param>
protected PeriodCountConsolidatorBase(PyObject pyObject)
: this(GetPeriodSpecificationFromPyObject(pyObject))
{
}
/// <summary>
/// Gets the type produced by this consolidator
/// </summary>
public override Type OutputType => typeof(TConsolidated);
/// <summary>
/// Gets a clone of the data being currently consolidated
/// </summary>
public override IBaseData WorkingData => _workingBar?.Clone();
/// <summary>
/// Event handler that fires when a new piece of data is produced. We define this as a 'new'
/// event so we can expose it as a <typeparamref name="TConsolidated"/> instead of a <see cref="BaseData"/> instance
/// </summary>
public new event EventHandler<TConsolidated> DataConsolidated;
/// <summary>
/// Updates this consolidator with the specified data. This method is
/// responsible for raising the DataConsolidated event
/// In time span mode, the bar range is closed on the left and open on the right: [T, T+TimeSpan).
/// For example, if time span is 1 minute, we have [10:00, 10:01): so data at 10:01 is not
/// included in the bar starting at 10:00.
/// </summary>
/// <exception cref="InvalidOperationException">Thrown when multiple symbols are being consolidated.</exception>
/// <param name="data">The new data for the consolidator</param>
public override void Update(T data)
{
if (!_securityIdentifierIsSet)
{
_securityIdentifierIsSet = true;
_securityIdentifier = data.Symbol.ID;
}
else if (!data.Symbol.ID.Equals(_securityIdentifier))
{
throw new InvalidOperationException($"Consolidators can only be used with a single symbol. The previous consolidated SecurityIdentifier ({_securityIdentifier}) is not the same as in the current data ({data.Symbol.ID}).");
}
if (!ShouldProcess(data))
{
// first allow the base class a chance to filter out data it doesn't want
// before we start incrementing counts and what not
return;
}
if (!_validateTimeSpan && _period.HasValue && _periodSpecification is TimeSpanPeriodSpecification)
{
// only do this check once
_validateTimeSpan = true;
var dataLength = data.EndTime - data.Time;
if (dataLength > _period)
{
throw new ArgumentException($"For Symbol {data.Symbol} can not consolidate bars of period: {_period}, using data of the same or higher period: {data.EndTime - data.Time}");
}
}
//Decide to fire the event
var fireDataConsolidated = false;
// decide to aggregate data before or after firing OnDataConsolidated event
// always aggregate before firing in counting mode
bool aggregateBeforeFire = _maxCount.HasValue;
if (_maxCount.HasValue)
{
// we're in count mode
_currentCount++;
if (_currentCount >= _maxCount.Value)
{
_currentCount = 0;
fireDataConsolidated = true;
}
}
if (!_lastEmit.HasValue)
{
// initialize this value for period computations
_lastEmit = IsTimeBased ? DateTime.MinValue : data.Time;
}
if (_period.HasValue)
{
// we're in time span mode and initialized
if (_workingBar != null && data.Time - _workingBar.Time >= _period.Value && GetRoundedBarTime(data) > _lastEmit)
{
fireDataConsolidated = true;
}
// special case: always aggregate before event trigger when TimeSpan is zero
if (_period.Value == TimeSpan.Zero)
{
fireDataConsolidated = true;
aggregateBeforeFire = true;
}
}
if (aggregateBeforeFire)
{
if (data.Time >= _lastEmit)
{
AggregateBar(ref _workingBar, data);
if (_maxCount.HasValue)
{
// When using count-based consolidation, set EndTime to the last input's EndTime
_workingBar.EndTime = data.EndTime;
}
}
}
//Fire the event
if (fireDataConsolidated)
{
var workingTradeBar = _workingBar as TradeBar;
if (workingTradeBar != null)
{
// we kind of are cheating here...
if (_period.HasValue)
{
workingTradeBar.Period = _period.Value;
}
}
// Set _lastEmit first because OnDataConsolidated will set _workingBar to null
_lastEmit = IsTimeBased && _workingBar != null ? _workingBar.EndTime : data.Time;
OnDataConsolidated(_workingBar);
}
if (!aggregateBeforeFire)
{
if (data.Time >= _lastEmit)
{
AggregateBar(ref _workingBar, data);
}
}
}
/// <summary>
/// Scans this consolidator to see if it should emit a bar due to time passing
/// </summary>
/// <param name="currentLocalTime">The current time in the local time zone (same as <see cref="BaseData.Time"/>)</param>
public override void Scan(DateTime currentLocalTime)
{
if (_workingBar != null && _period.HasValue && _period.Value != TimeSpan.Zero
&& currentLocalTime - _workingBar.Time >= _period.Value && GetRoundedBarTime(currentLocalTime) > _lastEmit)
{
_lastEmit = _workingBar.EndTime;
OnDataConsolidated(_workingBar);
}
}
/// <summary>
/// Resets the consolidator
/// </summary>
public override void Reset()
{
base.Reset();
_securityIdentifier = null;
_securityIdentifierIsSet = false;
_currentCount = 0;
_workingBar = null;
_lastEmit = null;
_validateTimeSpan = false;
}
/// <summary>
/// Returns true if this consolidator is time-based, false otherwise
/// </summary>
protected bool IsTimeBased => !_maxCount.HasValue;
/// <summary>
/// Gets the time period for this consolidator
/// </summary>
protected TimeSpan? Period => _period;
/// <summary>
/// Determines whether or not the specified data should be processed
/// </summary>
/// <param name="data">The data to check</param>
/// <returns>True if the consolidator should process this data, false otherwise</returns>
protected virtual bool ShouldProcess(T data) => true;
/// <summary>
/// Aggregates the new 'data' into the 'workingBar'. The 'workingBar' will be
/// null following the event firing
/// </summary>
/// <param name="workingBar">The bar we're building, null if the event was just fired and we're starting a new consolidated bar</param>
/// <param name="data">The new data</param>
protected abstract void AggregateBar(ref TConsolidated workingBar, T data);
/// <summary>
/// Gets a rounded-down bar time. Called by AggregateBar in derived classes.
/// </summary>
/// <param name="time">The bar time to be rounded down</param>
/// <returns>The rounded bar time</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected DateTime GetRoundedBarTime(DateTime time)
{
var startTime = _periodSpecification.GetRoundedBarTime(time);
// In the case of a new bar, define the period defined at opening time
if (_workingBar == null)
{
_period = _periodSpecification.Period;
}
return startTime;
}
/// <summary>
/// Gets a rounded-down bar start time. Called by AggregateBar in derived classes.
/// </summary>
/// <param name="inputData">The input data point</param>
/// <returns>The rounded bar start time</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected DateTime GetRoundedBarTime(IBaseData inputData)
{
var potentialStartTime = GetRoundedBarTime(inputData.Time);
if (_period.HasValue && potentialStartTime + _period < inputData.EndTime)
{
// US equity hour bars from the database starts at 9am but the exchange opens at 9:30am. Thus, the method
// GetRoundedBarTime(inputData.Time) returns the market open of the previous day, which is not consistent
// with the given end time. For that reason we need to handle this case specifically, by calling
// GetRoundedBarTime(inputData.EndTime) as it will return our expected start time: 9:30am
if (inputData.EndTime - inputData.Time == Time.OneHour && potentialStartTime.Date < inputData.Time.Date)
{
potentialStartTime = GetRoundedBarTime(inputData.EndTime);
}
else
{
// whops! the end time we were giving is beyond our potential end time, so let's use the giving bars star time instead
potentialStartTime = inputData.Time;
}
}
return potentialStartTime;
}
/// <summary>
/// Event invocator for the <see cref="DataConsolidated"/> event
/// </summary>
/// <param name="e">The consolidated data</param>
protected virtual void OnDataConsolidated(TConsolidated e)
{
base.OnDataConsolidated(e);
DataConsolidated?.Invoke(this, e);
ResetWorkingBar();
}
/// <summary>
/// Resets the working bar
/// </summary>
protected virtual void ResetWorkingBar()
{
_workingBar = null;
}
/// <summary>
/// Gets the period specification from the PyObject that can either represent a function object that defines the start time of a consolidated data or a timespan.
/// </summary>
/// <param name="pyObject">Python object that defines either a function object that defines the start time of a consolidated data or a timespan</param>
/// <returns>IPeriodSpecification that represents the PyObject</returns>
private static IPeriodSpecification GetPeriodSpecificationFromPyObject(PyObject pyObject)
{
Func<DateTime, CalendarInfo> expiryFunc;
if (pyObject.TrySafeAs(out expiryFunc))
{
return new FuncPeriodSpecification(expiryFunc);
}
using (Py.GIL())
{
return new TimeSpanPeriodSpecification(pyObject.As<TimeSpan>());
}
}
/// <summary>
/// Distinguishes between the different ways a consolidated data start time can be specified
/// </summary>
private interface IPeriodSpecification
{
TimeSpan? Period { get; }
DateTime GetRoundedBarTime(DateTime time);
}
/// <summary>
/// User defined the bars period using a counter
/// </summary>
private class BarCountPeriodSpecification : IPeriodSpecification
{
public TimeSpan? Period { get; } = null;
public DateTime GetRoundedBarTime(DateTime time) => time;
}
/// <summary>
/// User defined the bars period using a counter and a period (mixed mode)
/// </summary>
private class MixedModePeriodSpecification : IPeriodSpecification
{
public TimeSpan? Period { get; }
public MixedModePeriodSpecification(TimeSpan period)
{
Period = period;
}
public DateTime GetRoundedBarTime(DateTime time) => time;
}
/// <summary>
/// User defined the bars period using a time span
/// </summary>
private class TimeSpanPeriodSpecification : IPeriodSpecification
{
public TimeSpan? StartTime { get; }
public TimeSpan? Period { get; }
public TimeSpanPeriodSpecification(TimeSpan period, TimeSpan? startTime = null)
{
Period = period;
StartTime = startTime;
}
public DateTime GetRoundedBarTime(DateTime time)
{
if (StartTime.HasValue)
{
return LeanData.GetConsolidatorStartTime(Period.Value, StartTime.Value, time);
}
return Period.Value > Time.OneDay
? time // #4915 For periods larger than a day, don't use a rounding schedule.
: time.RoundDown(Period.Value);
}
}
/// <summary>
/// Special case for bars where the open time is defined by a function.
/// We assert on construction that the function returns a date time in the past or equal to the given time instant.
/// </summary>
private class FuncPeriodSpecification : IPeriodSpecification
{
private static readonly DateTime _verificationDate = new DateTime(2022, 01, 03, 10, 10, 10);
public TimeSpan? Period { get; private set; }
public readonly Func<DateTime, CalendarInfo> _calendarInfoFunc;
public FuncPeriodSpecification(Func<DateTime, CalendarInfo> expiryFunc)
{
if (expiryFunc(_verificationDate).Start > _verificationDate)
{
throw new ArgumentException($"{nameof(FuncPeriodSpecification)}: Please use a function that computes the start of the bar associated with the given date time. Should never return a time later than the one passed in.");
}
_calendarInfoFunc = expiryFunc;
}
public DateTime GetRoundedBarTime(DateTime time)
{
var calendarInfo = _calendarInfoFunc(time);
Period = calendarInfo.Period;
return calendarInfo.Start;
}
}
}
}
@@ -0,0 +1,132 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
using System;
using Python.Runtime;
using QuantConnect.Data.Market;
namespace QuantConnect.Data.Consolidators
{
/// <summary>
/// Consolidates QuoteBars into larger QuoteBars
/// </summary>
public class QuoteBarConsolidator : PeriodCountConsolidatorBase<QuoteBar, QuoteBar>
{
/// <summary>
/// Initializes a new instance of the <see cref="QuoteBarConsolidator"/> class
/// </summary>
/// <param name="period">The minimum span of time before emitting a consolidated bar</param>
/// <param name="startTime">Optionally the bar start time anchor to use</param>
public QuoteBarConsolidator(TimeSpan period, TimeSpan? startTime = null)
: base(period, startTime)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="QuoteBarConsolidator"/> class
/// </summary>
/// <param name="maxCount">The number of pieces to accept before emitting a consolidated bar</param>
public QuoteBarConsolidator(int maxCount)
: base(maxCount)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="QuoteBarConsolidator"/> class
/// </summary>
/// <param name="maxCount">The number of pieces to accept before emitting a consolidated bar</param>
/// <param name="period">The minimum span of time before emitting a consolidated bar</param>
public QuoteBarConsolidator(int maxCount, TimeSpan period)
: base(maxCount, period)
{
}
/// <summary>
/// Creates a consolidator to produce a new 'QuoteBar' representing the last count pieces of data or the period, whichever comes first
/// </summary>
/// <param name="func">Func that defines the start time of a consolidated data</param>
public QuoteBarConsolidator(Func<DateTime, CalendarInfo> func)
: base(func)
{
}
/// <summary>
/// Creates a consolidator to produce a new 'QuoteBar' representing the last count pieces of data or the period, whichever comes first
/// </summary>
/// <param name="pyfuncobj">Python function object that defines the start time of a consolidated data</param>
public QuoteBarConsolidator(PyObject pyfuncobj)
: base(pyfuncobj)
{
}
/// <summary>
/// Aggregates the new 'data' into the 'workingBar'. The 'workingBar' will be
/// null following the event firing
/// </summary>
/// <param name="workingBar">The bar we're building, null if the event was just fired and we're starting a new consolidated bar</param>
/// <param name="data">The new data</param>
protected override void AggregateBar(ref QuoteBar workingBar, QuoteBar data)
{
var bid = data.Bid;
var ask = data.Ask;
if (workingBar == null)
{
workingBar = new QuoteBar(GetRoundedBarTime(data), data.Symbol, null, 0, null, 0, IsTimeBased && Period.HasValue ? Period : data.Period);
// open ask and bid should match previous close ask and bid
if (Consolidated != null)
{
// note that we will only fill forward previous close ask and bid when a new data point comes in and we generate a new working bar which is not a fill forward bar
var previous = Consolidated as QuoteBar;
workingBar.Update(0, previous.Bid?.Close ?? 0, previous.Ask?.Close ?? 0, 0, previous.LastBidSize, previous.LastAskSize);
}
}
// update the bid and ask
if (bid != null)
{
workingBar.LastBidSize = data.LastBidSize;
if (workingBar.Bid == null)
{
workingBar.Bid = new Bar(bid.Open, bid.High, bid.Low, bid.Close);
}
else
{
workingBar.Bid.Close = bid.Close;
if (workingBar.Bid.High < bid.High) workingBar.Bid.High = bid.High;
if (workingBar.Bid.Low > bid.Low) workingBar.Bid.Low = bid.Low;
}
}
if (ask != null)
{
workingBar.LastAskSize = data.LastAskSize;
if (workingBar.Ask == null)
{
workingBar.Ask = new Bar(ask.Open, ask.High, ask.Low, ask.Close);
}
else
{
workingBar.Ask.Close = ask.Close;
if (workingBar.Ask.High < ask.High) workingBar.Ask.High = ask.High;
if (workingBar.Ask.Low > ask.Low) workingBar.Ask.Low = ask.Low;
}
}
workingBar.Value = data.Value;
}
}
}
@@ -0,0 +1,159 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using Python.Runtime;
using QuantConnect.Data.Market;
using QuantConnect.Securities;
using System;
namespace QuantConnect.Data.Consolidators
{
/// <summary>
/// This consolidator can transform a stream of <see cref="IBaseData"/> instances into a stream of <see cref="RangeBar"/>
/// </summary>
public class RangeConsolidator : BaseTimelessConsolidator<RangeBar>
{
private bool _firstTick;
private decimal _minimumPriceVariation;
/// <summary>
/// Symbol properties database to use to get the minimum price variation of certain symbol
/// </summary>
private static SymbolPropertiesDatabase _symbolPropertiesDatabase = SymbolPropertiesDatabase.FromDataFolder();
/// <summary>
/// Bar being created
/// </summary>
protected override RangeBar CurrentBar { get; set; }
/// <summary>
/// Range for each RangeBar, this is, the difference between the High and Low for each
/// RangeBar
/// </summary>
public decimal RangeSize { get; private set; }
/// <summary>
/// Number of MinimumPriceVariation units
/// </summary>
public int Range { get; private set; }
/// <summary>
/// Gets <see cref="RangeBar"/> which is the type emitted in the <see cref="IDataConsolidator.DataConsolidated"/> event.
/// </summary>
public override Type OutputType => typeof(RangeBar);
/// <summary>
/// Gets a clone of the data being currently consolidated
/// </summary>
public override IBaseData WorkingData => CurrentBar?.Clone();
/// <summary>
/// Initializes a new instance of the <see cref="RangeConsolidator" /> class.
/// </summary>
/// <param name="range">The Range interval sets the range in which the price moves, which in turn initiates the formation of a new bar.
/// One range equals to one minimum price change, where this last value is defined depending of the RangeBar's symbol</param>
/// <param name="selector">Extracts the value from a data instance to be formed into a <see cref="RangeBar"/>. The default
/// value is (x => x.Value) the <see cref="IBaseData.Value"/> property on <see cref="IBaseData"/></param>
/// <param name="volumeSelector">Extracts the volume from a data instance. The default value is null which does
/// not aggregate volume per bar, except if the input is a TradeBar.</param>
public RangeConsolidator(
int range,
Func<IBaseData, decimal> selector = null,
Func<IBaseData, decimal> volumeSelector = null)
: base(selector, volumeSelector)
{
Range = range;
_firstTick = true;
}
/// <summary>
/// Initializes a new instance of the <see cref="RangeConsolidator" /> class.
/// </summary>
/// <param name="range">The Range interval sets the range in which the price moves, which in turn initiates the formation of a new bar.
/// One range equals to one minimum price change, where this last value is defined depending of the RangeBar's symbol</param>
/// <param name="selector">Extracts the value from a data instance to be formed into a <see cref="RangeBar"/>. The default
/// value is (x => x.Value) the <see cref="IBaseData.Value"/> property on <see cref="IBaseData"/></param>
/// <param name="volumeSelector">Extracts the volume from a data instance. The default value is null which does
/// not aggregate volume per bar.</param>
public RangeConsolidator(int range,
PyObject selector,
PyObject volumeSelector = null)
: base(selector, volumeSelector)
{
Range = range;
_firstTick = true;
}
/// <summary>
/// Resets the consolidator
/// </summary>
public override void Reset()
{
base.Reset();
_firstTick = true;
_minimumPriceVariation = 0m;
RangeSize = 0m;
}
/// <summary>
/// Updates the current RangeBar being created with the given data.
/// Additionally, if it's the case, it consolidates the current RangeBar
/// </summary>
/// <param name="time">Time of the given data</param>
/// <param name="currentValue">Value of the given data</param>
/// <param name="volume">Volume of the given data</param>
protected override void UpdateBar(DateTime time, decimal currentValue, decimal volume)
{
bool isRising = default;
if (currentValue > CurrentBar.High)
{
isRising = true;
}
else if (currentValue < CurrentBar.Low)
{
isRising = false;
}
CurrentBar.Update(time, currentValue, volume);
while (CurrentBar.IsClosed)
{
OnDataConsolidated(CurrentBar);
CurrentBar = new RangeBar(CurrentBar.Symbol, CurrentBar.EndTime, RangeSize, isRising ? CurrentBar.High + _minimumPriceVariation : CurrentBar.Low - _minimumPriceVariation);
CurrentBar.Update(time, currentValue, Math.Abs(CurrentBar.Low - currentValue) > RangeSize ? 0 : volume); // Intermediate/phantom RangeBar's have zero volume
}
}
/// <summary>
/// Creates a new bar with the given data
/// </summary>
/// <param name="data">The new data for the bar</param>
/// <param name="currentValue">The new value for the bar</param>
/// <param name="volume">The new volume for the bar</param>
protected override void CreateNewBar(IBaseData data, decimal currentValue, decimal volume)
{
var open = currentValue;
if (_firstTick)
{
_minimumPriceVariation = _symbolPropertiesDatabase.GetSymbolProperties(data.Symbol.ID.Market, data.Symbol, data.Symbol.ID.SecurityType, "USD").MinimumPriceVariation;
RangeSize = _minimumPriceVariation * Range;
open = Math.Ceiling(open / RangeSize) * RangeSize;
_firstTick = false;
}
CurrentBar = new RangeBar(data.Symbol, data.Time, RangeSize, open, volume: volume);
}
}
}
@@ -0,0 +1,373 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using QuantConnect.Data.Market;
namespace QuantConnect.Data.Consolidators
{
/// <summary>
/// This consolidator can transform a stream of <see cref="BaseData"/> instances into a stream of <see cref="RenkoBar"/>
/// with Renko type <see cref="RenkoType.Wicked"/>.
/// </summary>
/// <remarks>This implementation replaced the original implementation that was shown to have inaccuracies in its representation
/// of Renko charts. The original implementation has been moved to <see cref="ClassicRenkoConsolidator"/>.</remarks>
public class RenkoConsolidator : ConsolidatorBase
{
private bool _firstTick = true;
private RenkoBar _lastWicko;
private RenkoBar _currentBar;
/// <summary>
/// Time of consolidated close.
/// </summary>
/// <remarks>Protected for testing</remarks>
protected DateTime CloseOn { get; set; }
/// <summary>
/// Value of consolidated close.
/// </summary>
/// <remarks>Protected for testing</remarks>
protected decimal CloseRate { get; set; }
/// <summary>
/// Value of consolidated high.
/// </summary>
/// <remarks>Protected for testing</remarks>
protected decimal HighRate { get; set; }
/// <summary>
/// Value of consolidated low.
/// </summary>
/// <remarks>Protected for testing</remarks>
protected decimal LowRate { get; set; }
/// <summary>
/// Time of consolidated open.
/// </summary>
/// <remarks>Protected for testing</remarks>
protected DateTime OpenOn { get; set; }
/// <summary>
/// Value of consolidate open.
/// </summary>
/// <remarks>Protected for testing</remarks>
protected decimal OpenRate { get; set; }
/// <summary>
/// Size of the consolidated bar.
/// </summary>
/// <remarks>Protected for testing</remarks>
protected decimal BarSize { get; set; }
/// <summary>
/// Gets the kind of the bar
/// </summary>
public RenkoType Type => RenkoType.Wicked;
/// <summary>
/// Gets a clone of the data being currently consolidated
/// </summary>
public override IBaseData WorkingData => _currentBar?.Clone();
/// <summary>
/// Gets the type consumed by this consolidator
/// </summary>
public override Type InputType => typeof(IBaseData);
/// <summary>
/// Gets <see cref="RenkoBar"/> which is the type emitted in the <see cref="IDataConsolidator.DataConsolidated"/> event.
/// </summary>
public override Type OutputType => typeof(RenkoBar);
/// <summary>
/// Typed event handler that fires when a new piece of data is produced
/// </summary>
public new event EventHandler<RenkoBar> DataConsolidated;
/// <summary>
/// Initializes a new instance of the <see cref="RenkoConsolidator"/> class using the specified <paramref name="barSize"/>.
/// </summary>
/// <param name="barSize">The constant value size of each bar</param>
public RenkoConsolidator(decimal barSize)
{
if (barSize <= 0)
{
throw new ArgumentException("Renko consolidator BarSize must be strictly greater than zero");
}
BarSize = barSize;
}
/// <summary>
/// Updates this consolidator with the specified data
/// </summary>
/// <param name="data">The new data for the consolidator</param>
public override void Update(IBaseData data)
{
var rate = data.Price;
if (_firstTick)
{
_firstTick = false;
// Round our first rate to the same length as BarSize
rate = GetClosestMultiple(rate, BarSize);
OpenOn = data.Time;
CloseOn = data.Time;
OpenRate = rate;
HighRate = rate;
LowRate = rate;
CloseRate = rate;
}
else
{
CloseOn = data.Time;
if (rate > HighRate)
{
HighRate = rate;
}
if (rate < LowRate)
{
LowRate = rate;
}
CloseRate = rate;
if (CloseRate > OpenRate)
{
if (_lastWicko == null || _lastWicko.Direction == BarDirection.Rising)
{
Rising(data);
return;
}
var limit = _lastWicko.Open + BarSize;
if (CloseRate > limit)
{
var wicko = new RenkoBar(data.Symbol, OpenOn, CloseOn, BarSize, _lastWicko.Open, limit,
LowRate, limit);
_lastWicko = wicko;
OnDataConsolidated(wicko);
OpenOn = CloseOn;
OpenRate = limit;
LowRate = limit;
Rising(data);
}
}
else if (CloseRate < OpenRate)
{
if (_lastWicko == null || _lastWicko.Direction == BarDirection.Falling)
{
Falling(data);
return;
}
var limit = _lastWicko.Open - BarSize;
if (CloseRate < limit)
{
var wicko = new RenkoBar(data.Symbol, OpenOn, CloseOn, BarSize, _lastWicko.Open, HighRate,
limit, limit);
_lastWicko = wicko;
OnDataConsolidated(wicko);
OpenOn = CloseOn;
OpenRate = limit;
HighRate = limit;
Falling(data);
}
}
}
}
/// <summary>
/// Scans this consolidator to see if it should emit a bar due to time passing
/// </summary>
/// <param name="currentLocalTime">The current time in the local time zone (same as <see cref="BaseData.Time"/>)</param>
public override void Scan(DateTime currentLocalTime)
{
}
/// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
/// <filterpriority>2</filterpriority>
public override void Dispose()
{
DataConsolidated = null;
base.Dispose();
}
/// <summary>
/// Resets the consolidator
/// </summary>
public override void Reset()
{
_firstTick = true;
_lastWicko = null;
_currentBar = null;
CloseOn = default;
CloseRate = default;
HighRate = default;
LowRate = default;
OpenOn = default;
OpenRate = default;
base.Reset();
}
/// <summary>
/// Raises the strongly typed DataConsolidated event
/// </summary>
/// <param name="consolidated">The newly consolidated data</param>
protected override void FireDataConsolidated(IBaseData consolidated)
{
var bar = (RenkoBar)consolidated;
// fire the typed event before updating the current bar so handlers reading
// WorkingData still see the previous bar, as they did before the rolling window
DataConsolidated?.Invoke(this, bar);
_currentBar = bar;
}
private void Rising(IBaseData data)
{
decimal limit;
while (CloseRate > (limit = OpenRate + BarSize))
{
var wicko = new RenkoBar(data.Symbol, OpenOn, CloseOn, BarSize, OpenRate, limit, LowRate, limit);
_lastWicko = wicko;
OnDataConsolidated(wicko);
OpenOn = CloseOn;
OpenRate = limit;
LowRate = limit;
}
}
private void Falling(IBaseData data)
{
decimal limit;
while (CloseRate < (limit = OpenRate - BarSize))
{
var wicko = new RenkoBar(data.Symbol, OpenOn, CloseOn, BarSize, OpenRate, HighRate, limit, limit);
_lastWicko = wicko;
OnDataConsolidated(wicko);
OpenOn = CloseOn;
OpenRate = limit;
HighRate = limit;
}
}
/// <summary>
/// Gets the closest BarSize-Multiple to the price.
/// </summary>
/// <remarks>Based on: The Art of Computer Programming, Vol I, pag 39. Donald E. Knuth</remarks>
/// <param name="price">Price to be rounded to the closest BarSize-Multiple</param>
/// <param name="barSize">The size of the Renko bar</param>
/// <returns>The closest BarSize-Multiple to the price</returns>
public static decimal GetClosestMultiple(decimal price, decimal barSize)
{
if (barSize <= 0)
{
throw new ArgumentException("BarSize must be strictly greater than zero");
}
var modulus = price - barSize * Math.Floor(price / barSize);
var round = Math.Round(modulus / barSize);
return barSize * (Math.Floor(price / barSize) + round);
}
}
/// <summary>
/// Provides a type safe wrapper on the RenkoConsolidator class. This just allows us to define our selector functions with the real type they'll be receiving
/// </summary>
/// <typeparam name="TInput"></typeparam>
public class RenkoConsolidator<TInput> : RenkoConsolidator
where TInput : IBaseData
{
/// <summary>
/// Initializes a new instance of the <see cref="RenkoConsolidator"/> class using the specified <paramref name="barSize"/>.
/// </summary>
/// <param name="barSize">The constant value size of each bar</param>
public RenkoConsolidator(decimal barSize)
: base(barSize)
{
}
/// <summary>
/// Updates this consolidator with the specified data.
/// </summary>
/// <remarks>
/// Type safe shim method.
/// </remarks>
/// <param name="data">The new data for the consolidator</param>
public void Update(TInput data)
{
base.Update(data);
}
}
/// <summary>
/// This consolidator can transform a stream of <see cref="BaseData"/> instances into a stream of <see cref="RenkoBar"/>
/// with Renko type <see cref="RenkoType.Wicked"/>.
/// /// </summary>
/// <remarks>For backwards compatibility now that WickedRenkoConsolidators -> RenkoConsolidator</remarks>
public class WickedRenkoConsolidator : RenkoConsolidator
{
/// <summary>
/// Initializes a new instance of the <see cref="RenkoConsolidator"/> class using the specified <paramref name="barSize"/>.
/// </summary>
/// <param name="barSize">The constant value size of each bar</param>
public WickedRenkoConsolidator(decimal barSize)
: base(barSize)
{
}
}
/// <summary>
/// This consolidator can transform a stream of <see cref="BaseData"/> instances into a stream of <see cref="RenkoBar"/>
/// with Renko type <see cref="RenkoType.Wicked"/>.
/// Provides a type safe wrapper on the WickedRenkoConsolidator class. This just allows us to define our selector functions with the real type they'll be receiving
/// /// </summary>
/// <remarks>For backwards compatibility now that WickedRenkoConsolidators -> RenkoConsolidator</remarks>
public class WickedRenkoConsolidator<T> : RenkoConsolidator<T>
where T : IBaseData
{
/// <summary>
/// Initializes a new instance of the <see cref="RenkoConsolidator"/> class using the specified <paramref name="barSize"/>.
/// </summary>
/// <param name="barSize">The constant value size of each bar</param>
public WickedRenkoConsolidator(decimal barSize)
: base(barSize)
{
}
}
}
@@ -0,0 +1,128 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
namespace QuantConnect.Data.Consolidators
{
/// <summary>
/// This consolidator wires up the events on its First and Second consolidators
/// such that data flows from the First to Second consolidator. It's output comes
/// from the Second.
/// </summary>
public class SequentialConsolidator : ConsolidatorBase
{
/// <summary>
/// Gets the first consolidator to receive data
/// </summary>
public IDataConsolidator First
{
get; private set;
}
/// <summary>
/// Gets the second consolidator that ends up receiving data produced
/// by the first
/// </summary>
public IDataConsolidator Second
{
get; private set;
}
/// <summary>
/// Gets a clone of the data being currently consolidated
/// </summary>
public override IBaseData WorkingData
{
get { return Second.WorkingData; }
}
/// <summary>
/// Gets the type consumed by this consolidator
/// </summary>
public override Type InputType
{
get { return First.InputType; }
}
/// <summary>
/// Gets the type produced by this consolidator
/// </summary>
public override Type OutputType
{
get { return Second.OutputType; }
}
/// <summary>
/// Updates this consolidator with the specified data
/// </summary>
/// <param name="data">The new data for the consolidator</param>
public override void Update(IBaseData data)
{
First.Update(data);
}
/// <summary>
/// Scans this consolidator to see if it should emit a bar due to time passing
/// </summary>
/// <param name="currentLocalTime">The current time in the local time zone (same as <see cref="BaseData.Time"/>)</param>
public override void Scan(DateTime currentLocalTime)
{
First.Scan(currentLocalTime);
}
/// <summary>
/// Creates a new consolidator that will pump date through the first, and then the output
/// of the first into the second. This enables 'wrapping' or 'composing' of consolidators
/// </summary>
/// <param name="first">The first consolidator to receive data</param>
/// <param name="second">The consolidator to receive first's output</param>
public SequentialConsolidator(IDataConsolidator first, IDataConsolidator second)
{
if (!second.InputType.IsAssignableFrom(first.OutputType))
{
throw new ArgumentException("first.OutputType must equal second.OutputType!");
}
First = first;
Second = second;
// wire up the second one to get data from the first
first.DataConsolidated += (sender, consolidated) => second.Update(consolidated);
// wire up the second one's events to also fire this consolidator's event so consumers
// can attach. This wrapper also keeps its own window
second.DataConsolidated += (sender, consolidated) => OnDataConsolidated(consolidated);
}
/// <summary>Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.</summary>
/// <filterpriority>2</filterpriority>
public override void Dispose()
{
First.Dispose();
Second.Dispose();
base.Dispose();
}
/// <summary>
/// Resets the consolidator
/// </summary>
public override void Reset()
{
First.Reset();
Second.Reset();
base.Reset();
}
}
}
@@ -0,0 +1,153 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using QuantConnect;
using QuantConnect.Data;
using QuantConnect.Securities;
using QuantConnect.Data.Market;
using QuantConnect.Data.Consolidators;
using QuantConnect.Util;
namespace Common.Data.Consolidators
{
/// <summary>
/// Consolidates intraday market data into a single daily <see cref="SessionBar"/> (OHLCV + OpenInterest).
/// </summary>
public class SessionConsolidator : PeriodCountConsolidatorBase<BaseData, SessionBar>
{
private readonly SecurityExchangeHours _exchangeHours;
private readonly TickType _sourceTickType;
private readonly Symbol _symbol;
private bool _initialized;
internal SessionBar WorkingInstance
{
get
{
if (_workingBar == null)
{
InitializeWorkingBar();
}
return _workingBar;
}
}
/// <summary>
/// Initializes a new instance of the <see cref="SessionConsolidator"/> class.
/// </summary>
/// <param name="exchangeHours">The exchange hours</param>
/// <param name="sourceTickType">Type of the source tick</param>
/// <param name="symbol">The symbol</param>
public SessionConsolidator(SecurityExchangeHours exchangeHours, TickType sourceTickType, Symbol symbol) : base(Time.OneDay)
{
_symbol = symbol;
_exchangeHours = exchangeHours;
_sourceTickType = sourceTickType;
InitializeWorkingBar();
}
/// <summary>
/// Aggregates the new 'data' into the 'workingBar'
/// </summary>
/// <param name="workingBar">The bar we're building, null if the event was just fired and we're starting a new trade bar</param>
/// <param name="data">The new data</param>
protected override void AggregateBar(ref SessionBar workingBar, BaseData data)
{
if (!_initialized)
{
if (workingBar.Time == DateTime.MaxValue || data.Time.Date > workingBar.Time.Date)
{
workingBar.Time = data.Time.Date;
}
_initialized = true;
}
// Handle open interest
if (data.DataType == MarketDataType.Tick && data is Tick oiTick && oiTick.TickType == TickType.OpenInterest)
{
// Update the working session bar with the latest open interest
workingBar.OpenInterest = oiTick.Value;
return;
}
if (!_exchangeHours.IsOpen(data.Time, data.EndTime, false))
{
return;
}
// Update the working session bar
workingBar.Update(data, Consolidated);
}
/// <summary>
/// Validates the current local time and triggers Scan() if a new day is detected.
/// </summary>
/// <param name="currentLocalTime">The current local time.</param>
public void ValidateAndScan(DateTime currentLocalTime)
{
if (!_initialized)
{
return;
}
if (currentLocalTime.Date != WorkingInstance.Time.Date)
{
Scan(currentLocalTime);
}
}
/// <summary>
/// Event handler that fires when a new piece of data is produced
/// </summary>
//public event DataConsolidatedHandler DataConsolidated;
protected override void OnDataConsolidated(SessionBar e)
{
_workingBar = null;
base.OnDataConsolidated(e);
}
/// <summary>
/// Resets the working bar
/// </summary>
protected override void ResetWorkingBar()
{
}
/// <summary>
/// Resets the consolidator
/// </summary>
public override void Reset()
{
base.Reset();
InitializeWorkingBar();
}
private void InitializeWorkingBar()
{
var time = DateTime.MaxValue;
if (Consolidated != null)
{
time = _exchangeHours.GetNextTradingDay(Consolidated.Time).Date;
}
_workingBar = new SessionBar(_sourceTickType)
{
Time = time,
Symbol = _symbol
};
_initialized = false;
}
}
}
@@ -0,0 +1,113 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using QuantConnect.Data.Market;
using Python.Runtime;
namespace QuantConnect.Data.Consolidators
{
/// <summary>
/// A data consolidator that can make bigger bars from ticks over a given
/// time span or a count of pieces of data.
/// </summary>
public class TickConsolidator : TradeBarConsolidatorBase<Tick>
{
/// <summary>
/// Creates a consolidator to produce a new 'TradeBar' representing the period
/// </summary>
/// <param name="period">The minimum span of time before emitting a consolidated bar</param>
public TickConsolidator(TimeSpan period)
: base(period)
{
}
/// <summary>
/// Creates a consolidator to produce a new 'TradeBar' representing the last count pieces of data
/// </summary>
/// <param name="maxCount">The number of pieces to accept before emitting a consolidated bar</param>
public TickConsolidator(int maxCount)
: base(maxCount)
{
}
/// <summary>
/// Creates a consolidator to produce a new 'TradeBar' representing the last count pieces of data or the period, whichever comes first
/// </summary>
/// <param name="maxCount">The number of pieces to accept before emitting a consolidated bar</param>
/// <param name="period">The minimum span of time before emitting a consolidated bar</param>
public TickConsolidator(int maxCount, TimeSpan period)
: base(maxCount, period)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="TickQuoteBarConsolidator"/> class
/// </summary>
/// <param name="func">Func that defines the start time of a consolidated data</param>
public TickConsolidator(Func<DateTime, CalendarInfo> func)
: base(func)
{
}
/// <summary>
/// Creates a consolidator to produce a new 'TradeBar' representing the last count pieces of data or the period, whichever comes first
/// </summary>
/// <param name="pyfuncobj">Python function object that defines the start time of a consolidated data</param>
public TickConsolidator(PyObject pyfuncobj)
: base(pyfuncobj)
{
}
/// <summary>
/// Determines whether or not the specified data should be processed
/// </summary>
/// <param name="data">The data to check</param>
/// <returns>True if the consolidator should process this data, false otherwise</returns>
protected override bool ShouldProcess(Tick data)
{
return data.TickType == TickType.Trade;
}
/// <summary>
/// Aggregates the new 'data' into the 'workingBar'. The 'workingBar' will be
/// null following the event firing
/// </summary>
/// <param name="workingBar">The bar we're building</param>
/// <param name="data">The new data</param>
protected override void AggregateBar(ref TradeBar workingBar, Tick data)
{
if (workingBar == null)
{
workingBar = new TradeBar(GetRoundedBarTime(data),
data.Symbol,
data.Value,
data.Value,
data.Value,
data.Value,
data.Quantity,
Period);
}
else
{
//Aggregate the working bar
workingBar.Close = data.Value;
workingBar.Volume += data.Quantity;
if (data.Value < workingBar.Low) workingBar.Low = data.Value;
if (data.Value > workingBar.High) workingBar.High = data.Value;
}
}
}
}
@@ -0,0 +1,111 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
using System;
using QuantConnect.Data.Market;
using Python.Runtime;
namespace QuantConnect.Data.Consolidators
{
/// <summary>
/// Consolidates ticks into quote bars. This consolidator ignores trade ticks
/// </summary>
public class TickQuoteBarConsolidator : PeriodCountConsolidatorBase<Tick, QuoteBar>
{
/// <summary>
/// Initializes a new instance of the <see cref="TickQuoteBarConsolidator"/> class
/// </summary>
/// <param name="period">The minimum span of time before emitting a consolidated bar</param>
/// <param name="startTime">Optionally the bar start time anchor to use</param>
public TickQuoteBarConsolidator(TimeSpan period, TimeSpan? startTime = null)
: base(period, startTime)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="TickQuoteBarConsolidator"/> class
/// </summary>
/// <param name="maxCount">The number of pieces to accept before emitting a consolidated bar</param>
public TickQuoteBarConsolidator(int maxCount)
: base(maxCount)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="TickQuoteBarConsolidator"/> class
/// </summary>
/// <param name="maxCount">The number of pieces to accept before emitting a consolidated bar</param>
/// <param name="period">The minimum span of time before emitting a consolidated bar</param>
public TickQuoteBarConsolidator(int maxCount, TimeSpan period)
: base(maxCount, period)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="TickQuoteBarConsolidator"/> class
/// </summary>
/// <param name="func">Func that defines the start time of a consolidated data</param>
public TickQuoteBarConsolidator(Func<DateTime, CalendarInfo> func)
: base(func)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="TickQuoteBarConsolidator"/> class
/// </summary>
/// <param name="pyfuncobj">Python function object that defines the start time of a consolidated data</param>
public TickQuoteBarConsolidator(PyObject pyfuncobj)
: base(pyfuncobj)
{
}
/// <summary>
/// Determines whether or not the specified data should be processed
/// </summary>
/// <param name="data">The data to check</param>
/// <returns>True if the consolidator should process this data, false otherwise</returns>
protected override bool ShouldProcess(Tick data)
{
return data.TickType == TickType.Quote;
}
/// <summary>
/// Aggregates the new 'data' into the 'workingBar'. The 'workingBar' will be
/// null following the event firing
/// </summary>
/// <param name="workingBar">The bar we're building, null if the event was just fired and we're starting a new consolidated bar</param>
/// <param name="data">The new data</param>
protected override void AggregateBar(ref QuoteBar workingBar, Tick data)
{
if (workingBar == null)
{
workingBar = new QuoteBar(GetRoundedBarTime(data), data.Symbol, null, decimal.Zero, null, decimal.Zero, Period);
// open ask and bid should match previous close ask and bid
if (Consolidated != null)
{
// note that we will only fill forward previous close ask and bid when a new data point comes in and we generate a new working bar which is not a fill forward bar
var previous = Consolidated as QuoteBar;
workingBar.Update(decimal.Zero, previous.Bid?.Close ?? decimal.Zero, previous.Ask?.Close ?? decimal.Zero, decimal.Zero, previous.LastBidSize, previous.LastAskSize);
}
}
// update the bid and ask
workingBar.Update(decimal.Zero, data.BidPrice, data.AskPrice, decimal.Zero, data.BidSize, data.AskSize);
if (!Period.HasValue) workingBar.EndTime = GetRoundedBarTime(data.EndTime);
}
}
}
@@ -0,0 +1,122 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
using System;
using QuantConnect.Data.Market;
using Python.Runtime;
namespace QuantConnect.Data.Consolidators
{
/// <summary>
/// A data consolidator that can make bigger bars from smaller ones over a given
/// time span or a count of pieces of data.
///
/// Use this consolidator to turn data of a lower resolution into data of a higher resolution,
/// for example, if you subscribe to minute data but want to have a 15 minute bar.
/// </summary>
public class TradeBarConsolidator : TradeBarConsolidatorBase<TradeBar>
{
/// <summary>
/// Create a new TradeBarConsolidator for the desired resolution
/// </summary>
/// <param name="resolution">The resolution desired</param>
/// <returns>A consolidator that produces data on the resolution interval</returns>
public static TradeBarConsolidator FromResolution(Resolution resolution)
{
return new TradeBarConsolidator(resolution.ToTimeSpan());
}
/// <summary>
/// Creates a consolidator to produce a new 'TradeBar' representing the period
/// </summary>
/// <param name="period">The minimum span of time before emitting a consolidated bar</param>
/// <param name="startTime">Optionally the bar start time anchor to use</param>
public TradeBarConsolidator(TimeSpan period, TimeSpan? startTime = null)
: base(period, startTime)
{
}
/// <summary>
/// Creates a consolidator to produce a new 'TradeBar' representing the last count pieces of data
/// </summary>
/// <param name="maxCount">The number of pieces to accept before emitting a consolidated bar</param>
public TradeBarConsolidator(int maxCount)
: base(maxCount)
{
}
/// <summary>
/// Creates a consolidator to produce a new 'TradeBar' representing the last count pieces of data or the period, whichever comes first
/// </summary>
/// <param name="maxCount">The number of pieces to accept before emitting a consolidated bar</param>
/// <param name="period">The minimum span of time before emitting a consolidated bar</param>
public TradeBarConsolidator(int maxCount, TimeSpan period)
: base(maxCount, period)
{
}
/// <summary>
/// Creates a consolidator to produce a new 'TradeBar' representing the last count pieces of data or the period, whichever comes first
/// </summary>
/// <param name="func">Func that defines the start time of a consolidated data</param>
public TradeBarConsolidator(Func<DateTime, CalendarInfo> func)
: base(func)
{
}
/// <summary>
/// Creates a consolidator to produce a new 'TradeBar' representing the last count pieces of data or the period, whichever comes first
/// </summary>
/// <param name="pyfuncobj">Python function object that defines the start time of a consolidated data</param>
public TradeBarConsolidator(PyObject pyfuncobj)
: base(pyfuncobj)
{
}
/// <summary>
/// Aggregates the new 'data' into the 'workingBar'. The 'workingBar' will be
/// null following the event firing
/// </summary>
/// <param name="workingBar">The bar we're building, null if the event was just fired and we're starting a new trade bar</param>
/// <param name="data">The new data</param>
protected override void AggregateBar(ref TradeBar workingBar, TradeBar data)
{
if (workingBar == null)
{
workingBar = new TradeBar
{
Time = GetRoundedBarTime(data),
Symbol = data.Symbol,
Open = data.Open,
High = data.High,
Low = data.Low,
Close = data.Close,
Volume = data.Volume,
DataType = MarketDataType.TradeBar,
Period = IsTimeBased && Period.HasValue ? (TimeSpan)Period : data.Period
};
}
else
{
//Aggregate the working bar
workingBar.Close = data.Close;
workingBar.Volume += data.Volume;
if (data.Low < workingBar.Low) workingBar.Low = data.Low;
if (data.High > workingBar.High) workingBar.High = data.High;
}
}
}
}
@@ -0,0 +1,84 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
using System;
using QuantConnect.Data.Market;
using Python.Runtime;
namespace QuantConnect.Data.Consolidators
{
/// <summary>
/// A data consolidator that can make bigger bars from any base data
///
/// This type acts as the base for other consolidators that produce bars on a given time step or for a count of data.
/// </summary>
/// <typeparam name="T">The input type into the consolidator's Update method</typeparam>
public abstract class TradeBarConsolidatorBase<T> : PeriodCountConsolidatorBase<T, TradeBar>
where T : IBaseData
{
/// <summary>
/// Creates a consolidator to produce a new 'TradeBar' representing the period
/// </summary>
/// <param name="period">The minimum span of time before emitting a consolidated bar</param>
/// <param name="startTime">Optionally the bar start time anchor to use</param>
protected TradeBarConsolidatorBase(TimeSpan period, TimeSpan? startTime = null)
: base(period, startTime)
{
}
/// <summary>
/// Creates a consolidator to produce a new 'TradeBar' representing the last count pieces of data
/// </summary>
/// <param name="maxCount">The number of pieces to accept before emiting a consolidated bar</param>
protected TradeBarConsolidatorBase(int maxCount)
: base(maxCount)
{
}
/// <summary>
/// Creates a consolidator to produce a new 'TradeBar' representing the last count pieces of data or the period, whichever comes first
/// </summary>
/// <param name="maxCount">The number of pieces to accept before emiting a consolidated bar</param>
/// <param name="period">The minimum span of time before emitting a consolidated bar</param>
protected TradeBarConsolidatorBase(int maxCount, TimeSpan period)
: base(maxCount, period)
{
}
/// <summary>
/// Creates a consolidator to produce a new 'TradeBar' representing the last count pieces of data or the period, whichever comes first
/// </summary>
/// <param name="func">Func that defines the start time of a consolidated data</param>
protected TradeBarConsolidatorBase(Func<DateTime, CalendarInfo> func)
: base(func)
{
}
/// <summary>
/// Creates a consolidator to produce a new 'TradeBar' representing the last count pieces of data or the period, whichever comes first
/// </summary>
/// <param name="pyfuncobj">Python function object that defines the start time of a consolidated data</param>
protected TradeBarConsolidatorBase(PyObject pyfuncobj)
: base(pyfuncobj)
{
}
/// <summary>
/// Gets a copy of the current 'workingBar'.
/// </summary>
public TradeBar WorkingBar => (TradeBar) WorkingData;
}
}
@@ -0,0 +1,149 @@
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using QuantConnect.Data.Market;
namespace QuantConnect.Data.Consolidators
{
/// <summary>
/// This consolidator can transform a stream of <see cref="BaseData"/> instances into a stream of <see cref="RenkoBar"/>
/// with a constant volume for each bar.
/// </summary>
public class VolumeRenkoConsolidator : DataConsolidator<BaseData>
{
private VolumeRenkoBar _currentBar;
private decimal _barSize;
/// <summary>
/// Gets a clone of the data being currently consolidated
/// </summary>
public override IBaseData WorkingData => _currentBar;
/// <summary>
/// Gets <see cref="VolumeRenkoBar"/> which is the type emitted in the <see cref="IDataConsolidator.DataConsolidated"/> event.
/// </summary>
public override Type OutputType => typeof(VolumeRenkoBar);
/// <summary>
/// Event handler that fires when a new piece of data is produced
/// </summary>
public new event EventHandler<VolumeRenkoBar> DataConsolidated;
/// <summary>
/// Initializes a new instance of the <see cref="VolumeRenkoConsolidator"/> class using the specified <paramref name="barSize"/>.
/// </summary>
/// <param name="barSize">The constant volume size of each bar</param>
public VolumeRenkoConsolidator(decimal barSize)
{
_barSize = barSize;
}
/// <summary>
/// Updates this consolidator with the specified data
/// </summary>
/// <param name="data">The new data for the consolidator</param>
public override void Update(BaseData data)
{
var close = data.Price;
var dataType = data.GetType();
decimal volume;
decimal open;
decimal high;
decimal low;
if (dataType == typeof(TradeBar))
{
var tradeBar = (TradeBar)data;
volume = tradeBar.Volume;
open = tradeBar.Open;
high = tradeBar.High;
low = tradeBar.Low;
}
else if (dataType == typeof(Tick))
{
var tick = (Tick)data;
// Only include actual trade information
if (tick.TickType != TickType.Trade)
{
return;
}
volume = tick.Quantity;
open = close;
high = close;
low = close;
}
else
{
throw new ArgumentException($"{GetType().Name} must be used with TradeBar or Tick data.");
}
var adjustedVolume = AdjustVolume(volume, close);
if (_currentBar == null)
{
_currentBar = new VolumeRenkoBar(data.Symbol, data.Time, data.EndTime, _barSize, open, high, low, close, 0);
}
var volumeLeftOver = _currentBar.Update(data.EndTime, high, low, close, adjustedVolume);
while (volumeLeftOver >= 0)
{
OnDataConsolidated(_currentBar);
_currentBar = _currentBar.Rollover();
volumeLeftOver = _currentBar.Update(data.EndTime, high, low, close, volumeLeftOver);
}
}
/// <summary>
/// Returns the raw volume without any adjustment.
/// </summary>
/// <param name="volume">The volume</param>
/// <param name="price">The price</param>
/// <returns>The unmodified volume</returns>
protected virtual decimal AdjustVolume(decimal volume, decimal price)
{
return volume;
}
/// <summary>
/// Scans this consolidator to see if it should emit a bar due to time passing
/// </summary>
/// <param name="currentLocalTime">The current time in the local time zone (same as <see cref="BaseData.Time"/>)</param>
public override void Scan(DateTime currentLocalTime)
{
}
/// <summary>
/// Resets the consolidator
/// </summary>
public override void Reset()
{
base.Reset();
_currentBar = null;
}
/// <summary>
/// Event invocator for the DataConsolidated event. This should be invoked
/// by derived classes when they have consolidated a new piece of data.
/// </summary>
/// <param name="consolidated">The newly consolidated data</param>
protected void OnDataConsolidated(VolumeRenkoBar consolidated)
{
base.OnDataConsolidated(consolidated);
DataConsolidated?.Invoke(this, consolidated);
}
}
}