/* * 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.Algorithm.Framework.Alphas; using QuantConnect.Data.UniverseSelection; using System; using System.Collections.Generic; namespace QuantConnect.Algorithm.Framework.Portfolio { /// /// Provides an implementation of that wraps a object /// public class PortfolioConstructionModelPythonWrapper : PortfolioConstructionModel { private readonly dynamic _model; private readonly bool _implementsDetermineTargetPercent; /// /// True if should rebalance portfolio on security changes. True by default /// public override bool RebalanceOnSecurityChanges { get { using (Py.GIL()) { return _model.RebalanceOnSecurityChanges; } } set { using (Py.GIL()) { _model.RebalanceOnSecurityChanges = value; } } } /// /// True if should rebalance portfolio on new insights or expiration of insights. True by default /// public override bool RebalanceOnInsightChanges { get { using (Py.GIL()) { return _model.RebalanceOnInsightChanges; } } set { using (Py.GIL()) { _model.RebalanceOnInsightChanges = value; } } } /// /// Constructor for initialising the class with wrapped object /// /// Model defining how to build a portfolio from alphas public PortfolioConstructionModelPythonWrapper(PyObject model) { using (Py.GIL()) { foreach (var attributeName in new[] { "CreateTargets", "OnSecuritiesChanged" }) { if (!model.HasAttr(attributeName)) { throw new NotImplementedException($"IPortfolioConstructionModel.{attributeName} must be implemented. Please implement this missing method on {model.GetPythonType()}"); } } _model = model; _model.SetPythonWrapper(this); _implementsDetermineTargetPercent = model.GetPythonMethod("DetermineTargetPercent") != null; } } /// /// Create portfolio targets from the specified insights /// /// The algorithm instance /// The insights to create portfolio targets from /// An enumerable of portfolio targets to be sent to the execution model public override IEnumerable CreateTargets(QCAlgorithm algorithm, Insight[] insights) { using (Py.GIL()) { foreach (var target in _model.CreateTargets(algorithm, insights)) { yield return target; } } } /// /// Event fired each time the we add/remove securities from the data feed /// /// The algorithm instance that experienced the change in securities /// The security additions and removals from the algorithm public override void OnSecuritiesChanged(QCAlgorithm algorithm, SecurityChanges changes) { using (Py.GIL()) { _model.OnSecuritiesChanged(algorithm, changes); } } /// /// Method that will determine if the portfolio construction model should create a /// target for this insight /// /// The insight to create a target for /// True if the portfolio should create a target for the insight protected override bool ShouldCreateTargetForInsight(Insight insight) { using (Py.GIL()) { return _model.ShouldCreateTargetForInsight(insight); } } /// /// Determines if the portfolio should be rebalanced base on the provided rebalancing func, /// if any security change have been taken place or if an insight has expired or a new insight arrived /// If the rebalancing function has not been provided will return true. /// /// The insights to create portfolio targets from /// The current algorithm UTC time /// True if should rebalance protected override bool IsRebalanceDue(Insight[] insights, DateTime algorithmUtc) { using (Py.GIL()) { return _model.IsRebalanceDue(insights, algorithmUtc); } } /// /// Gets the target insights to calculate a portfolio target percent for /// /// An enumerable of the target insights protected override List GetTargetInsights() { using (Py.GIL()) { return _model.GetTargetInsights(); } } /// /// Will determine the target percent for each insight /// /// The active insights to generate a target for /// A target percent for each insight protected override Dictionary DetermineTargetPercent(List activeInsights) { using (Py.GIL()) { if (!_implementsDetermineTargetPercent) { // the implementation is in C# return _model.DetermineTargetPercent(activeInsights); } Dictionary dic; var result = _model.DetermineTargetPercent(activeInsights); if ((result as PyObject).TryConvert(out dic)) { // this is required if the python implementation is actually returning a C# dic, not common, // but could happen if its actually calling a base C# implementation return dic; } dic = new Dictionary(); foreach (var pyInsight in result) { var insight = (pyInsight as PyObject).As(); dic[insight] = result[pyInsight]; } return dic; } } } }