Lean  $LEAN_TAG$
AccumulativeInsightPortfolioConstructionModel.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 System;
17 using System.Collections.Generic;
18 using System.Linq;
19 using Python.Runtime;
22 
24 {
25  /// <summary>
26  /// Provides an implementation of <see cref="IPortfolioConstructionModel"/> that allocates percent of account
27  /// to each insight, defaulting to 3%.
28  /// For insights of direction <see cref="InsightDirection.Up"/>, long targets are returned and
29  /// for insights of direction <see cref="InsightDirection.Down"/>, short targets are returned.
30  /// By default, no rebalancing shall be done.
31  /// Rules:
32  /// 1. On active Up insight, increase position size by percent
33  /// 2. On active Down insight, decrease position size by percent
34  /// 3. On active Flat insight, move by percent towards 0
35  /// 4. On expired insight, and no other active insight, emits a 0 target'''
36  /// </summary>
38  {
39  private readonly PortfolioBias _portfolioBias;
40  private readonly double _percent;
41 
42  /// <summary>
43  /// Initialize a new instance of <see cref="AccumulativeInsightPortfolioConstructionModel"/>
44  /// </summary>
45  /// <param name="rebalancingDateRules">The date rules used to define the next expected rebalance time
46  /// in UTC</param>
47  /// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
48  /// <param name="percent">The percentage amount of the portfolio value to allocate
49  /// to a single insight. The value of percent should be in the range [0,1].
50  /// The default value is 0.03.</param>
52  PortfolioBias portfolioBias = PortfolioBias.LongShort,
53  double percent = 0.03)
54  : this(rebalancingDateRules.ToFunc(), portfolioBias, percent)
55  {
56  }
57 
58  /// <summary>
59  /// Initialize a new instance of <see cref="AccumulativeInsightPortfolioConstructionModel"/>
60  /// </summary>
61  /// <param name="rebalancingFunc">For a given algorithm UTC DateTime returns the next expected rebalance time
62  /// or null if unknown, in which case the function will be called again in the next loop. Returning current time
63  /// will trigger rebalance. If null will be ignored</param>
64  /// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
65  /// <param name="percent">The percentage amount of the portfolio value to allocate
66  /// to a single insight. The value of percent should be in the range [0,1].
67  /// The default value is 0.03.</param>
68  public AccumulativeInsightPortfolioConstructionModel(Func<DateTime, DateTime?> rebalancingFunc = null,
69  PortfolioBias portfolioBias = PortfolioBias.LongShort,
70  double percent = 0.03)
71  : base(rebalancingFunc)
72  {
73  _portfolioBias = portfolioBias;
74  _percent = Math.Abs(percent);
75  }
76 
77  /// <summary>
78  /// Initialize a new instance of <see cref="AccumulativeInsightPortfolioConstructionModel"/>
79  /// </summary>
80  /// <param name="rebalancingFunc">For a given algorithm UTC DateTime returns the next expected rebalance UTC time.
81  /// Returning current time will trigger rebalance. If null will be ignored</param>
82  /// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
83  /// <param name="percent">The percentage amount of the portfolio value to allocate
84  /// to a single insight. The value of percent should be in the range [0,1].
85  /// The default value is 0.03.</param>
86  public AccumulativeInsightPortfolioConstructionModel(Func<DateTime, DateTime> rebalancingFunc,
87  PortfolioBias portfolioBias = PortfolioBias.LongShort,
88  double percent = 0.03)
89  : this(rebalancingFunc != null ? (Func<DateTime, DateTime?>)(timeUtc => rebalancingFunc(timeUtc)) : null,
90  portfolioBias,
91  percent)
92  {
93  }
94 
95  /// <summary>
96  /// Initialize a new instance of <see cref="AccumulativeInsightPortfolioConstructionModel"/>
97  /// </summary>
98  /// <param name="rebalance">Rebalancing func or if a date rule, timedelta will be converted into func.
99  /// For a given algorithm UTC DateTime the func returns the next expected rebalance time
100  /// or null if unknown, in which case the function will be called again in the next loop. Returning current time
101  /// will trigger rebalance. If null will be ignored</param>
102  /// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
103  /// <remarks>This is required since python net can not convert python methods into func nor resolve the correct
104  /// constructor for the date rules parameter.
105  /// For performance we prefer python algorithms using the C# implementation</remarks>
106  /// <param name="percent">The percentage amount of the portfolio value to allocate
107  /// to a single insight. The value of percent should be in the range [0,1].
108  /// The default value is 0.03.</param>
110  PortfolioBias portfolioBias = PortfolioBias.LongShort,
111  double percent = 0.03)
112  : this((Func<DateTime, DateTime?>)null,
113  portfolioBias,
114  percent)
115  {
116  SetRebalancingFunc(rebalance);
117  }
118 
119  /// <summary>
120  /// Initialize a new instance of <see cref="AccumulativeInsightPortfolioConstructionModel"/>
121  /// </summary>
122  /// <param name="timeSpan">Rebalancing frequency</param>
123  /// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
124  /// <param name="percent">The percentage amount of the portfolio value to allocate
125  /// to a single insight. The value of percent should be in the range [0,1].
126  /// The default value is 0.03.</param>
128  PortfolioBias portfolioBias = PortfolioBias.LongShort,
129  double percent = 0.03)
130  : this(dt => dt.Add(timeSpan), portfolioBias, percent)
131  {
132  }
133 
134  /// <summary>
135  /// Initialize a new instance of <see cref="AccumulativeInsightPortfolioConstructionModel"/>
136  /// </summary>
137  /// <param name="resolution">Rebalancing frequency</param>
138  /// <param name="portfolioBias">Specifies the bias of the portfolio (Short, Long/Short, Long)</param>
139  /// <param name="percent">The percentage amount of the portfolio value to allocate
140  /// to a single insight. The value of percent should be in the range [0,1].
141  /// The default value is 0.03.</param>
143  PortfolioBias portfolioBias = PortfolioBias.LongShort,
144  double percent = 0.03)
145  : this(resolution.ToTimeSpan(), portfolioBias, percent)
146  {
147  }
148 
149  /// <summary>
150  /// Gets the target insights to calculate a portfolio target percent for
151  /// </summary>
152  /// <returns>An enumerable of the target insights</returns>
153  protected override List<Insight> GetTargetInsights()
154  {
155  return Algorithm.Insights.GetActiveInsights(Algorithm.UtcTime).Where(ShouldCreateTargetForInsight)
156  .OrderBy(insight => insight.GeneratedTimeUtc)
157  .ToList();
158  }
159 
160  /// <summary>
161  /// Determines the target percent for each insight
162  /// </summary>
163  /// <param name="activeInsights">The active insights to generate a target for</param>
164  /// <returns>A target percent for each insight</returns>
165  protected override Dictionary<Insight, double> DetermineTargetPercent(List<Insight> activeInsights)
166  {
167  var percentPerSymbol = new Dictionary<Symbol, double>();
168 
169  foreach (var insight in activeInsights)
170  {
171  double targetPercent;
172  if (percentPerSymbol.TryGetValue(insight.Symbol, out targetPercent))
173  {
174  if (insight.Direction == InsightDirection.Flat)
175  {
176  // We received a Flat
177  // if adding or subtracting will push past 0, then make it 0
178  if (Math.Abs(targetPercent) < _percent)
179  {
180  targetPercent = 0;
181  }
182  else
183  {
184  // otherwise, we flatten by percent
185  targetPercent += (targetPercent > 0 ? -_percent : _percent);
186  }
187  }
188  }
189  targetPercent += _percent * (int)insight.Direction;
190 
191  // adjust to respect portfolio bias
192  if (_portfolioBias != PortfolioBias.LongShort
193  && Math.Sign(targetPercent) != (int)_portfolioBias)
194  {
195  targetPercent = 0;
196  }
197 
198  percentPerSymbol[insight.Symbol] = targetPercent;
199  }
200 
201  return activeInsights.DistinctBy(insight => insight.Symbol)
202  .ToDictionary(insight => insight, insight => percentPerSymbol[insight.Symbol]);
203  }
204  }
205 }