diff --git a/Common/Securities/SecurityCache.cs b/Common/Securities/SecurityCache.cs index f59b580230c2..e66ab017110c 100644 --- a/Common/Securities/SecurityCache.cs +++ b/Common/Securities/SecurityCache.cs @@ -19,6 +19,7 @@ using QuantConnect.Data.Market; using System.Collections.Generic; using System.Runtime.CompilerServices; +using QuantConnect.Python; using QuantConnect.Data.Fundamental; using QuantConnect.Data.UniverseSelection; using QuantConnect.Util; @@ -268,6 +269,36 @@ protected virtual void ProcessDataPoint(BaseData data, bool cacheByType) Price = data.Price; } } + + var pythonData = data as PythonData; + if (pythonData != null && (_lastQuoteBarUpdate != data.EndTime || _lastOHLCUpdate != data.EndTime) && isDefaultDataType) + { + IDictionary storage = pythonData.GetStorageDictionary(); + List validFields = new List { "open", "high", "low", "close", "volume" }; + IDictionary validOHLC = new Dictionary(); + + foreach (string fieldName in validFields) + { + if (!storage.ContainsKey(fieldName)) + validOHLC.Add(fieldName, 0); + else + { + string match = storage[fieldName].ToString(); + decimal.TryParse(match, out decimal result); + validOHLC.Add(fieldName, result); + } + } + _lastOHLCUpdate = data.EndTime; + if (validOHLC["open"] != 0) Open = validOHLC["open"]; + if (validOHLC["high"] != 0) High = validOHLC["high"]; + if (validOHLC["low"] != 0) Low = validOHLC["low"]; + if (validOHLC["volume"] != 0) Volume = validOHLC["volume"]; + if (validOHLC["close"] != 0) + { + Price = validOHLC["close"]; + Close = validOHLC["close"]; + } + } } /// diff --git a/Tests/Common/Securities/SecurityCacheTests.cs b/Tests/Common/Securities/SecurityCacheTests.cs index e5c759868d8f..b66a4aafaf8f 100644 --- a/Tests/Common/Securities/SecurityCacheTests.cs +++ b/Tests/Common/Securities/SecurityCacheTests.cs @@ -17,12 +17,15 @@ using System.Collections.Generic; using System.Linq; using Newtonsoft.Json.Linq; +using NodaTime; using NUnit.Framework; +using Python.Runtime; using QuantConnect.Algorithm.CSharp; using QuantConnect.Data; using QuantConnect.Data.Fundamental; using QuantConnect.Data.Market; using QuantConnect.Data.UniverseSelection; +using QuantConnect.Python; using QuantConnect.Securities; using QuantConnect.Tests.Common.Data.Fundamental; @@ -359,6 +362,69 @@ public void AddDataEquity_OHLC_IgnoresQuoteBar() Assert.AreEqual(110, securityCache.BidSize); } + [Test] + public void AddDataEquity_OHLC_PythonData() + { + using (Py.GIL()) + { + dynamic testModule = PyModule.FromString("testModule", + @" +from AlgorithmImports import * + +class CustomDataTest(PythonData): + def Reader(self, config, line, date, isLiveMode): + result = CustomDataTest() + result.Symbol = config.Symbol + result.Value = 3.7 + result.Open = 3.1 + result.High = 4.1 + result.low = 2.0 + result.close = 3.7 + result.volume = 33 + result.Time = datetime.strptime(""2022-05-05"", ""%Y-%m-%d"") + return result"); + + var data = GetDataFromModule(testModule); + + var securityCache = new SecurityCache(); + securityCache.AddData(data); + + Assert.AreEqual(4.1, securityCache.High); + Assert.AreEqual(3.7, securityCache.Close); + Assert.AreEqual(2.0, securityCache.Low); + Assert.AreEqual(3.1, securityCache.Open); + Assert.AreEqual(33, securityCache.Volume); + + testModule = PyModule.FromString("testModule", + @" +from AlgorithmImports import * + +class CustomDataTest(PythonData): + def Reader(self, config, line, date, isLiveMode): + result = CustomDataTest() + result.Symbol = config.Symbol + result.Value = 3.7 + result.Open = 3.1 + result.High = 4 + result.low = 2.0 + result.Close = ""test"" + result.volume = 0 + result.Time = datetime.strptime(""2022-05-05"", ""%Y-%m-%d"") + return result"); + + data = GetDataFromModule(testModule); + + securityCache.Reset(); + securityCache.AddData(data); + + Assert.AreEqual(4, securityCache.High); + Assert.AreEqual(0, securityCache.Close); + Assert.AreEqual(2.0, securityCache.Low); + Assert.AreEqual(3.1, securityCache.Open); + Assert.AreEqual(0, securityCache.Volume); + } + } + [Test] [TestCaseSource(nameof(GetSecurityCacheInitialStates))] public void AddDataWithSameEndTime_SetsQuoteBarValues(SecurityCache cache, SecuritySeedData seedType) @@ -456,6 +522,15 @@ public void GetAllData_ReturnsListOfDataOnTargetCache() Assert.AreEqual(2m, data[1].Ask); } + private static BaseData GetDataFromModule(dynamic testModule) + { + var type = Extensions.CreateType(testModule.GetAttr("CustomDataTest")); + var customDataTest = new PythonData(testModule.GetAttr("CustomDataTest")()); + var config = new SubscriptionDataConfig(type, Symbols.SPY, Resolution.Daily, DateTimeZone.Utc, + DateTimeZone.Utc, false, false, false, isCustom: true); + return customDataTest.Reader(config, "something", DateTime.UtcNow, false); + } + private void AddDataAndAssertChanges(SecurityCache cache, SecuritySeedData seedType, SecuritySeedData dataType, BaseData data, Dictionary cacheToBaseDataPropertyMap = null) { var before = JObject.FromObject(cache);