diff --git a/Algorithm.CSharp/AltData/USTreasuryYieldCurveRateAlgorithm.cs b/Algorithm.CSharp/AltData/USTreasuryYieldCurveRateAlgorithm.cs new file mode 100644 index 000000000..b54cad659 --- /dev/null +++ b/Algorithm.CSharp/AltData/USTreasuryYieldCurveRateAlgorithm.cs @@ -0,0 +1,81 @@ +/* + * 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.Linq; +using QuantConnect.Data; +using QuantConnect.Data.Custom.USTreasury; + +namespace QuantConnect.Algorithm.CSharp +{ + public class USTreasuryYieldCurveRateAlgorithm : QCAlgorithm + { + private Symbol _yieldCurve; + private Symbol _spy; + private DateTime _lastInversion = DateTime.MinValue; + + public override void Initialize() + { + SetStartDate(2000, 3, 1); + SetEndDate(2019, 9, 15); + SetCash(100000); + + _spy = AddEquity("SPY", Resolution.Hour).Symbol; + _yieldCurve = AddData("YIELDCURVE").Symbol; + } + + public override void OnData(Slice data) + { + if (!data.ContainsKey(_yieldCurve)) + { + return; + } + + // Preserve null values by getting the data with `slice.Get` + // Accessing the data using `data[_yieldCurve]` results in null + // values becoming `default(decimal)` which is equal to 0 + var rates = data.Get().Values.First(); + + // Check for null before using the values + if (!rates.TenYear.HasValue || !rates.TwoYear.HasValue) + { + return; + } + + // Only advance if a year has gone by + if (Time - _lastInversion < TimeSpan.FromDays(365)) + { + return; + } + + // if there is a yield curve inversion after not having one for a year, short SPY for two years + if (!Portfolio.Invested && rates.TwoYear > rates.TenYear) + { + Debug($"{Time} - Yield curve inversion! Shorting the market for two years"); + SetHoldings(_spy, -0.5); + + _lastInversion = Time; + + return; + } + + // If two years have passed, liquidate our position in SPY + if (Time - _lastInversion >= TimeSpan.FromDays(365 * 2)) + { + Liquidate(_spy); + } + } + } +} diff --git a/Algorithm.CSharp/QuantConnect.Algorithm.CSharp.csproj b/Algorithm.CSharp/QuantConnect.Algorithm.CSharp.csproj index 0897af8fe..0904a33fe 100644 --- a/Algorithm.CSharp/QuantConnect.Algorithm.CSharp.csproj +++ b/Algorithm.CSharp/QuantConnect.Algorithm.CSharp.csproj @@ -1,4 +1,4 @@ - + @@ -152,6 +152,7 @@ + diff --git a/Algorithm.Python/AltData/USTreasuryYieldCurveRateAlgorithm.py b/Algorithm.Python/AltData/USTreasuryYieldCurveRateAlgorithm.py new file mode 100644 index 000000000..0d4b5b34b --- /dev/null +++ b/Algorithm.Python/AltData/USTreasuryYieldCurveRateAlgorithm.py @@ -0,0 +1,65 @@ +# 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. + +from clr import AddReference +AddReference("System") +AddReference("QuantConnect.Algorithm") +AddReference("QuantConnect.Common") + +from System import * +from QuantConnect import * +from QuantConnect.Algorithm import * +from QuantConnect.Data import * +from QuantConnect.Data.Custom.USTreasury import * + +from datetime import datetime, timedelta + +class USTreasuryYieldCurveRateAlgorithm(QCAlgorithm): + + def Initialize(self): + + self.SetStartDate(2000, 3, 1) + self.SetEndDate(2019, 9, 15) + self.SetCash(100000) + + self.spy = self.AddEquity("SPY", Resolution.Hour).Symbol + self.yieldCurve = self.AddData(USTreasuryYieldCurveRate, "YIELDCURVE").Symbol + self.lastInversion = datetime(1, 1, 1) + + def OnData(self, data): + + if not data.ContainsKey(self.yieldCurve): + return + + rates = data[self.yieldCurve] + + # Check for None before using the values + if rates.TenYear is None or rates.TwoYear is None: + return + + # Only advance if a year has gone by + if (self.Time - self.lastInversion) < timedelta(days=365): + return + + # if there is a yield curve inversion after not having one for a year, short SPY for two years + if not self.Portfolio.Invested and rates.TwoYear > rates.TenYear: + self.Debug(f"{self.Time} - Yield curve inversion! Shorting the market for two years") + self.SetHoldings(self.spy, -0.5) + + self.lastInversion = self.Time + + return + + # If two years have passed, liquidate our position in SPY + if self.Time - self.lastInversion >= timedelta(days=365 * 2): + self.Liquidate(self.spy) diff --git a/Algorithm.Python/QuantConnect.Algorithm.Python.csproj b/Algorithm.Python/QuantConnect.Algorithm.Python.csproj index 066033ff1..a1f78bc89 100644 --- a/Algorithm.Python/QuantConnect.Algorithm.Python.csproj +++ b/Algorithm.Python/QuantConnect.Algorithm.Python.csproj @@ -1,4 +1,4 @@ - + @@ -61,6 +61,7 @@ + diff --git a/Indicators/PythonIndicator.cs b/Indicators/PythonIndicator.cs index 968a7fd10..543bb3bd3 100644 --- a/Indicators/PythonIndicator.cs +++ b/Indicators/PythonIndicator.cs @@ -117,7 +117,7 @@ namespace QuantConnect.Indicators { using (Py.GIL()) { - _isReady = _indicator.Update(input); + _isReady = _indicator.Update(input) ?? _indicator.IsReady; return _indicator.Value; } } diff --git a/Tests/Indicators/PythonIndicatorNoinheritanceTests.cs b/Tests/Indicators/PythonIndicatorNoinheritanceTests.cs index d35d45af7..0a4867bdc 100644 --- a/Tests/Indicators/PythonIndicatorNoinheritanceTests.cs +++ b/Tests/Indicators/PythonIndicatorNoinheritanceTests.cs @@ -1,11 +1,11 @@ /* * 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"); + * + * 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. @@ -14,10 +14,8 @@ */ using System; -using System.Collections.Generic; using System.Globalization; using System.IO; -using System.Linq; using NUnit.Framework; using Python.Runtime; using QuantConnect.Data; @@ -28,6 +26,9 @@ namespace QuantConnect.Tests.Indicators [TestFixture] public class PythonIndicatorNoinheritanceTests : PythonIndicatorTests { + /// + /// In this Custom Indicator, Update returns a boolean + /// protected override IndicatorBase CreateIndicator() { using (Py.GIL()) diff --git a/Tests/Indicators/PythonIndicatorNoinheritanceTestsLegacy.cs b/Tests/Indicators/PythonIndicatorNoinheritanceTestsLegacy.cs new file mode 100644 index 000000000..cf71285cd --- /dev/null +++ b/Tests/Indicators/PythonIndicatorNoinheritanceTestsLegacy.cs @@ -0,0 +1,112 @@ +/* + * 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.Globalization; +using System.IO; +using NUnit.Framework; +using Python.Runtime; +using QuantConnect.Data; +using QuantConnect.Indicators; + +namespace QuantConnect.Tests.Indicators +{ + [TestFixture] + public class PythonIndicatorNoinheritanceTestsLegacy : PythonIndicatorTests + { + /// + /// In this Custom Indicator, Update returns void + /// + protected override IndicatorBase CreateIndicator() + { + using (Py.GIL()) + { + var module = PythonEngine.ModuleFromString( + Guid.NewGuid().ToString(), + @" +from collections import deque +from datetime import datetime, timedelta +from numpy import sum + +class CustomSimpleMovingAverage(): + def __init__(self, name, period): + self.Name = name + self.Value = 0 + self.IsReady = False + self.queue = deque(maxlen=period) + + # Update method is mandatory + def Update(self, input): + self.queue.appendleft(input.Value) + count = len(self.queue) + self.Value = sum(self.queue) / count + self.IsReady = count == self.queue.maxlen +" + ); + var indicator = module.GetAttr("CustomSimpleMovingAverage") + .Invoke("custom".ToPython(), 14.ToPython()); + + return new PythonIndicator(indicator); + } + } + + protected override void RunTestIndicator(IndicatorBase indicator) + { + var first = true; + var closeIndex = -1; + var targetIndex = -1; + foreach (var line in File.ReadLines(Path.Combine("TestData", TestFileName))) + { + var parts = line.Split(new[] { ',' }, StringSplitOptions.None); + + if (first) + { + first = false; + for (var i = 0; i < parts.Length; i++) + { + if (parts[i].Trim() == "Close") + { + closeIndex = i; + } + if (parts[i].Trim() == TestColumnName) + { + targetIndex = i; + } + } + if (closeIndex * targetIndex < 0) + { + Assert.Fail($"Didn't find one of 'Close' or '{line}' in the header: ", TestColumnName); + } + + continue; + } + + var close = decimal.Parse(parts[closeIndex], CultureInfo.InvariantCulture); + var date = Time.ParseDate(parts[0]); + + var data = new IndicatorDataPoint(date, close); + indicator.Update(data); + + if (!indicator.IsReady || parts[targetIndex].Trim() == string.Empty) + { + continue; + } + + var expected = double.Parse(parts[targetIndex], CultureInfo.InvariantCulture); + Assertion.Invoke(indicator, expected); + } + } + } +} \ No newline at end of file diff --git a/Tests/QuantConnect.Tests.csproj b/Tests/QuantConnect.Tests.csproj index ce91ef506..4418e9dd8 100644 --- a/Tests/QuantConnect.Tests.csproj +++ b/Tests/QuantConnect.Tests.csproj @@ -355,6 +355,7 @@ +