/* * 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 NUnit.Framework; using Python.Runtime; using QuantConnect.Statistics; using System.Collections.Generic; using System.Linq; namespace QuantConnect.Tests.Common { [TestFixture] public class ExtendedDictionaryTests { [Test] public void RunPythonDictionaryFeatureRegressionAlgorithm() { var parameter = new RegressionTests.AlgorithmStatisticsTestParameters("PythonDictionaryFeatureRegressionAlgorithm", new Dictionary { {PerformanceMetrics.TotalOrders, "3"}, {"Average Win", "0%"}, {"Average Loss", "0%"}, {"Compounding Annual Return", "349.409%"}, {"Drawdown", "2.600%"}, {"Expectancy", "0"}, {"Net Profit", "1.940%"}, {"Sharpe Ratio", "10.771"}, {"Probabilistic Sharpe Ratio", "66.098%"}, {"Loss Rate", "0%"}, {"Win Rate", "0%"}, {"Profit-Loss Ratio", "0"}, {"Alpha", "0.571"}, {"Beta", "1.247"}, {"Annual Standard Deviation", "0.282"}, {"Annual Variance", "0.079"}, {"Information Ratio", "14.457"}, {"Tracking Error", "0.073"}, {"Treynor Ratio", "2.433"}, {"Total Fees", "$0.00"}, {"OrderListHash", "a91c50e19b6b9ee19007be4555029779"} }, Language.Python, AlgorithmStatus.Completed); AlgorithmRunner.RunLocalBacktest(parameter.Algorithm, parameter.Statistics, parameter.Language, parameter.ExpectedFinalStatus, initialCash: 100000); } [Test] public void ExtendedDictionaryBehavesAsPythonDictionary() { using var _ = Py.GIL(); var module = PyModule.FromString("ExtendedDictionaryBehavesAsPythonDictionary", @" def contains(dictionary, key): return key in dictionary def get(dictionary, key): return dictionary.get(key) def keys(dictionary): return dictionary.keys() def values(dictionary): return dictionary.values() def pop(dictionary, key): return dictionary.pop(key) "); var dict = new TestDictionary { ["a"] = 1, ["b"] = 2, ["c"] = 3 }; using var pyDict = dict.ToPython(); var expectedKeys = new[] { "a", "b", "c" }; var keys = module.InvokeMethod("keys", pyDict).GetAndDispose>(); CollectionAssert.AreEquivalent(expectedKeys, keys); var expectedValues = new[] { 1, 2, 3 }; var values = module.InvokeMethod("values", pyDict).GetAndDispose>(); CollectionAssert.AreEquivalent(expectedValues, values); foreach (var (key, value) in keys.Zip(values)) { using var pyKey = key.ToPython(); Assert.IsTrue(module.InvokeMethod("contains", pyDict, pyKey).As()); Assert.AreEqual(value, module.InvokeMethod("get", pyDict, pyKey).As()); } using var pyNonExistingKey = "d".ToPython(); Assert.IsFalse(module.InvokeMethod("contains", pyDict, pyNonExistingKey).As()); Assert.IsFalse(module.InvokeMethod("contains", pyDict, PyObject.None).As()); using var pyExistingKey = keys[0].ToPython(); using var pyExistingValue = values[0].ToPython(); var popped = module.InvokeMethod("pop", pyDict, pyExistingKey).As(); Assert.AreEqual(1, popped); Assert.IsFalse(module.InvokeMethod("contains", pyDict, pyExistingKey).As()); } [Test] public void SymbolKeyCanBeIndexedWithStrings() { using var _ = Py.GIL(); var module = PyModule.FromString("SymbolKeyCanBeIndexedWithStrings", @" def get(dictionary, key): return dictionary[key] def set(dictionary, key, value): dictionary[key] = value "); var symbol = Symbols.SPY; using var pySymbol = symbol.ToPython(); SymbolCache.Set(symbol.Value, symbol); var dict = new TestDictionary { [symbol] = 1, }; using var pyDict = dict.ToPython(); var value = module.InvokeMethod("get", pyDict, pySymbol).As(); Assert.AreEqual(1, value); using var pyStringSymbol = symbol.Value.ToPython(); value = module.InvokeMethod("get", pyDict, pyStringSymbol).As(); Assert.AreEqual(1, value); using var pyNewValue = 2.ToPython(); module.InvokeMethod("set", pyDict, pySymbol, pyNewValue); value = module.InvokeMethod("get", pyDict, pySymbol).As(); Assert.AreEqual(2, value); value = module.InvokeMethod("get", pyDict, pyStringSymbol).As(); Assert.AreEqual(2, value); using var pyNewValue2 = 3.ToPython(); module.InvokeMethod("set", pyDict, pyStringSymbol, pyNewValue2); value = module.InvokeMethod("get", pyDict, pySymbol).As(); Assert.AreEqual(3, value); value = module.InvokeMethod("get", pyDict, pyStringSymbol).As(); Assert.AreEqual(3, value); using var pyNonExistingSymbol = Symbols.AAPL.ToPython(); using var pyStringNonExistingSymbol = Symbols.AAPL.Value.ToPython(); var exception = Assert.Throws(() => module.InvokeMethod("get", pyDict, pyNonExistingSymbol)); Assert.IsInstanceOf(exception.InnerException); exception = Assert.Throws(() => module.InvokeMethod("get", pyDict, pyStringNonExistingSymbol)); Assert.IsInstanceOf(exception.InnerException); } private class TestDictionary : ExtendedDictionary { private readonly Dictionary _data = new(); public override int Count => _data.Count; public override bool IsReadOnly => false; public override TValue this[TKey key] { get => _data[key]; set => _data[key] = value; } protected override IEnumerable GetKeys => _data.Keys; protected override IEnumerable GetValues => _data.Values; public override bool TryGetValue(TKey key, out TValue value) { return _data.TryGetValue(key, out value); } public override bool ContainsKey(TKey key) { return _data.ContainsKey(key); } public override bool Remove(TKey key) { return _data.Remove(key); } public override IEnumerable> GetItems() { return _data; } } private class TestSymbolDictionary : TestDictionary { } } }