Lean  $LEAN_TAG$
PortfolioConstructionModelPythonWrapper.cs
1 /*
2  * QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
3  * Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14 */
15 
16 using Python.Runtime;
19 using QuantConnect.Python;
20 using System;
21 using System.Collections.Generic;
22 
24 {
25  /// <summary>
26  /// Provides an implementation of <see cref="IPortfolioConstructionModel"/> that wraps a <see cref="PyObject"/> object
27  /// </summary>
29  {
30  private readonly BasePythonWrapper<PortfolioConstructionModel> _model;
31  private readonly bool _implementsDetermineTargetPercent;
32 
33  /// <summary>
34  /// True if should rebalance portfolio on security changes. True by default
35  /// </summary>
36  public override bool RebalanceOnSecurityChanges
37  {
38  get
39  {
40  return _model.GetProperty<bool>(nameof(RebalanceOnSecurityChanges));
41  }
42  set
43  {
44  _model.SetProperty(nameof(RebalanceOnSecurityChanges), value);
45  }
46  }
47 
48  /// <summary>
49  /// True if should rebalance portfolio on new insights or expiration of insights. True by default
50  /// </summary>
51  public override bool RebalanceOnInsightChanges
52  {
53  get
54  {
55  return _model.GetProperty<bool>(nameof(RebalanceOnInsightChanges));
56  }
57  set
58  {
59  _model.SetProperty(nameof(RebalanceOnInsightChanges), value);
60  }
61  }
62 
63  /// <summary>
64  /// Constructor for initialising the <see cref="IPortfolioConstructionModel"/> class with wrapped <see cref="PyObject"/> object
65  /// </summary>
66  /// <param name="model">Model defining how to build a portfolio from alphas</param>
68  {
69  _model = new BasePythonWrapper<PortfolioConstructionModel>(model, false);
70  using (Py.GIL())
71  {
72  foreach (var attributeName in new[] { "CreateTargets", "OnSecuritiesChanged" })
73  {
74  if (!_model.HasAttr(attributeName))
75  {
76  throw new NotImplementedException($"IPortfolioConstructionModel.{attributeName} must be implemented. Please implement this missing method on {model.GetPythonType()}");
77  }
78  }
79 
80  _model.InvokeMethod(nameof(SetPythonWrapper), this).Dispose();
81 
82  _implementsDetermineTargetPercent = model.GetPythonMethod("DetermineTargetPercent") != null;
83  }
84  }
85 
86  /// <summary>
87  /// Create portfolio targets from the specified insights
88  /// </summary>
89  /// <param name="algorithm">The algorithm instance</param>
90  /// <param name="insights">The insights to create portfolio targets from</param>
91  /// <returns>An enumerable of portfolio targets to be sent to the execution model</returns>
92  public override IEnumerable<IPortfolioTarget> CreateTargets(QCAlgorithm algorithm, Insight[] insights)
93  {
94  return _model.InvokeMethod<IEnumerable<IPortfolioTarget>>(nameof(CreateTargets), algorithm, insights);
95  }
96 
97  /// <summary>
98  /// Event fired each time the we add/remove securities from the data feed
99  /// </summary>
100  /// <param name="algorithm">The algorithm instance that experienced the change in securities</param>
101  /// <param name="changes">The security additions and removals from the algorithm</param>
102  public override void OnSecuritiesChanged(QCAlgorithm algorithm, SecurityChanges changes)
103  {
104  _model.InvokeMethod(nameof(OnSecuritiesChanged), algorithm, changes).Dispose();
105  }
106 
107  /// <summary>
108  /// Method that will determine if the portfolio construction model should create a
109  /// target for this insight
110  /// </summary>
111  /// <param name="insight">The insight to create a target for</param>
112  /// <returns>True if the portfolio should create a target for the insight</returns>
113  protected override bool ShouldCreateTargetForInsight(Insight insight)
114  {
115  return _model.InvokeMethod<bool>(nameof(ShouldCreateTargetForInsight), insight);
116  }
117 
118  /// <summary>
119  /// Determines if the portfolio should be rebalanced base on the provided rebalancing func,
120  /// if any security change have been taken place or if an insight has expired or a new insight arrived
121  /// If the rebalancing function has not been provided will return true.
122  /// </summary>
123  /// <param name="insights">The insights to create portfolio targets from</param>
124  /// <param name="algorithmUtc">The current algorithm UTC time</param>
125  /// <returns>True if should rebalance</returns>
126  protected override bool IsRebalanceDue(Insight[] insights, DateTime algorithmUtc)
127  {
128  return _model.InvokeMethod<bool>(nameof(IsRebalanceDue), insights, algorithmUtc);
129  }
130 
131  /// <summary>
132  /// Gets the target insights to calculate a portfolio target percent for
133  /// </summary>
134  /// <returns>An enumerable of the target insights</returns>
135  protected override List<Insight> GetTargetInsights()
136  {
137  return _model.InvokeMethod<List<Insight>>(nameof(GetTargetInsights));
138  }
139 
140  /// <summary>
141  /// Will determine the target percent for each insight
142  /// </summary>
143  /// <param name="activeInsights">The active insights to generate a target for</param>
144  /// <returns>A target percent for each insight</returns>
145  protected override Dictionary<Insight, double> DetermineTargetPercent(List<Insight> activeInsights)
146  {
147  using (Py.GIL())
148  {
149  if (!_implementsDetermineTargetPercent)
150  {
151  // the implementation is in C#
152  return _model.InvokeMethod<Dictionary<Insight, double>>(nameof(DetermineTargetPercent), activeInsights);
153  }
154 
155  Dictionary<Insight, double> dic;
156  var result = _model.InvokeMethod(nameof(DetermineTargetPercent), activeInsights) as dynamic;
157  if ((result as PyObject).TryConvert(out dic))
158  {
159  // this is required if the python implementation is actually returning a C# dic, not common,
160  // but could happen if its actually calling a base C# implementation
161  return dic;
162  }
163 
164  dic = new Dictionary<Insight, double>();
165  foreach (var pyInsight in result)
166  {
167  var insight = (pyInsight as PyObject).As<Insight>();
168  dic[insight] = result[pyInsight];
169  }
170 
171  return dic;
172  }
173  }
174  }
175 }