/* * 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 System.Collections.Generic; using System.Linq; using QuantConnect.Indicators; namespace QuantConnect.Algorithm.Framework.Portfolio { /// /// Contains returns specific to a symbol required for optimization model /// public class ReturnsSymbolData { private readonly Symbol _symbol; private readonly RateOfChange _roc; private readonly RollingWindow _window; /// /// The symbol's asset rate of change indicator /// public RateOfChange ROC { get { return _roc; } } /// /// Initializes a new instance of the class /// /// The symbol of the data that updates the indicators /// Look-back period for the RateOfChange indicator /// Size of rolling window that contains historical RateOfChange public ReturnsSymbolData(Symbol symbol, int lookback, int period) { _symbol = symbol; _roc = new RateOfChange($"{_symbol}.ROC({lookback})", lookback); _window = new RollingWindow(period); _roc.Updated += OnRateOfChangeUpdated; } /// /// Historical returns /// public Dictionary Returns => _window.ToDictionary(x => x.EndTime, x => (double) x.Value); /// /// Adds an item to this window and shifts all other elements /// /// The time associated with the value /// The value to use to update this window public void Add(DateTime time, decimal value) { var item = new IndicatorDataPoint(_symbol, time, value); AddToWindow(item); } /// /// Updates the state of the RateOfChange with the given value and returns true /// if this indicator is ready, false otherwise /// /// The time associated with the value /// The value to use to update this indicator /// True if this indicator is ready, false otherwise public bool Update(DateTime time, decimal value) { return _roc.Update(time, value); } /// /// Resets all indicators of this object to its initial state /// public void Reset() { _roc.Updated -= OnRateOfChangeUpdated; _roc.Reset(); _window.Reset(); } /// /// When the RateOfChange is updated, adds the new value to the RollingWindow /// /// /// private void OnRateOfChangeUpdated(object roc, IndicatorDataPoint updated) { if (_roc.IsReady) { AddToWindow(updated); } } private void AddToWindow(IndicatorDataPoint updated) { if (_window.Samples > 0 && _window[0].EndTime == updated.EndTime) { // this could happen with fill forward bars in the history request return; } _window.Add(updated); } } /// /// Extension methods for /// public static class ReturnsSymbolDataExtensions { /// /// Converts a dictionary of keyed by into a matrix /// /// Dictionary of keyed by to be converted into a matrix /// List of to be included in the matrix public static double[,] FormReturnsMatrix(this Dictionary symbolData, IEnumerable symbols) { var returnsByDate = (from s in symbols join sd in symbolData on s equals sd.Key select sd.Value.Returns).ToList(); // Consolidate by date var alldates = returnsByDate.SelectMany(r => r.Keys).Distinct().ToList(); var max = symbolData.Count == 0 ? 0 : symbolData.Max(kvp => kvp.Value.Returns.Count); // Perfect match between the dates in the ReturnsSymbolData objects if (max == alldates.Count) { return Accord.Math.Matrix.Create(alldates // if a return date isn't found for a symbol we use 'double.NaN' .Select(d => returnsByDate.Select(s => s.GetValueOrDefault(d, double.NaN)).ToArray()) .Where(r => !r.Select(Math.Abs).Sum().IsNaNOrZero()) // remove empty rows .ToArray()); } // If it is not a match, we assume that each index correspond to the same point in time var returnsByIndex = returnsByDate.Select((doubles, i) => doubles.Values.ToArray()); return Accord.Math.Matrix.Create(Enumerable.Range(0, max) // there is no guarantee that all symbols have the same amount of returns so we need to check range and use 'double.NaN' if required as above .Select(d => returnsByIndex.Select(s => s.Length < (d + 1) ? double.NaN : s[d]).ToArray()) .Where(r => !r.Select(Math.Abs).Sum().IsNaNOrZero()) // remove empty rows .ToArray()); } } }