20 using System.Collections.Generic;
22 using Accord.Statistics;
43 private readonly
double _riskFreeRate;
44 private readonly
double _delta;
45 private readonly
int _lookback;
46 private readonly
double _tau;
47 private readonly
int _period;
49 private readonly Dictionary<Symbol, ReturnsSymbolData> _symbolDataDict;
68 double riskFreeRate = 0.0,
72 : this(dt => dt.Add(timeSpan), portfolioBias, lookback, period, resolution, riskFreeRate, delta, tau, optimizer)
93 double riskFreeRate = 0.0,
97 : this(rebalanceResolution.ToTimeSpan(), portfolioBias, lookback, period, resolution, riskFreeRate, delta, tau, optimizer)
119 double riskFreeRate = 0.0,
123 : this(rebalancingFunc != null ? (Func<DateTime, DateTime?>)(timeUtc => rebalancingFunc(timeUtc)) : null,
153 double riskFreeRate = 0.0,
157 : this(rebalancingDateRules.ToFunc(), portfolioBias, lookback, period, resolution, riskFreeRate, delta, tau, optimizer)
184 double riskFreeRate = 0.0,
188 : this((Func<DateTime, DateTime?>)null, portfolioBias, lookback, period, resolution, riskFreeRate, delta, tau, optimizer)
212 double riskFreeRate = 0.0,
216 : base(rebalancingFunc)
218 _lookback = lookback;
220 _resolution = resolution;
221 _riskFreeRate = riskFreeRate;
228 _portfolioBias = portfolioBias;
229 _symbolDataDict =
new Dictionary<Symbol, ReturnsSymbolData>();
250 var targets =
new Dictionary<Insight, double>();
252 if (
TryGetViews(activeInsights, out var P, out var Q))
255 foreach (var insight
in activeInsights)
257 if (_symbolDataDict.TryGetValue(insight.Symbol, out var symbolData))
259 if (insight.Magnitude ==
null)
261 Algorithm.SetRunTimeError(
new ArgumentNullException(
"BlackLittermanOptimizationPortfolioConstructionModel does not accept \'null\' as Insight.Magnitude. Please make sure your Alpha Model is generating Insights with the Magnitude property set."));
264 symbolData.Add(insight.GeneratedTimeUtc, insight.Magnitude.Value.SafeDecimalCast());
268 var symbols = activeInsights.Select(x => x.Symbol).Distinct().ToList();
269 var returns = _symbolDataDict.FormReturnsMatrix(symbols);
274 ApplyBlackLittermanMasterFormula(ref Π, ref Σ, P, Q);
277 var W = _optimizer.Optimize(returns, Π, Σ);
279 foreach (var symbol
in symbols)
281 var weight = W[sidx];
285 && Math.Sign(weight) != (
int)_portfolioBias)
289 targets[activeInsights.First(insight => insight.Symbol == symbol)] = weight;
308 return (from insight in activeInsights
309 group insight by
new { insight.Symbol, insight.SourceModel } into g
310 select g.OrderBy(x => x.GeneratedTimeUtc).Last())
311 .OrderBy(x => x.Symbol).ToList();
321 base.OnSecuritiesChanged(algorithm, changes);
323 foreach (var symbol
in changes.RemovedSecurities.Select(x => x.Symbol))
325 if (_symbolDataDict.ContainsKey(symbol))
327 _symbolDataDict[symbol].Reset();
328 _symbolDataDict.Remove(symbol);
333 var addedSymbols = changes.AddedSecurities.ToDictionary(x => x.Symbol, x => x.Exchange.TimeZone);
334 algorithm.History(addedSymbols.Keys, _lookback * _period, _resolution)
338 if (!_symbolDataDict.TryGetValue(bar.Symbol, out symbolData))
340 symbolData = new ReturnsSymbolData(bar.Symbol, _lookback, _period);
341 _symbolDataDict.Add(bar.Symbol, symbolData);
344 var utcTime = bar.EndTime.ConvertToUtc(addedSymbols[bar.Symbol]);
345 symbolData.
Update(utcTime, bar.Value);
358 var W = Vector.Create(returns.GetLength(1), 1.0 / returns.GetLength(1));
360 Σ = returns.Covariance().Multiply(252);
362 var annualReturn = W.Dot(Elementwise.Add(returns.Mean(0), 1.0).Pow(252.0).Subtract(1.0));
364 var annualVariance = W.Dot(Σ.Dot(W));
366 var riskAversion = (annualReturn - _riskFreeRate) / annualVariance;
368 return Σ.Dot(W).Multiply(riskAversion);
377 protected bool TryGetViews(ICollection<Insight> insights, out
double[,] P, out
double[] Q)
381 var symbols = insights.Select(insight => insight.Symbol).ToHashSet();
383 var tmpQ = insights.GroupBy(insight => insight.SourceModel)
386 var upInsightsSum = values.Where(i => i.Direction ==
InsightDirection.Up).Sum(i => Math.Abs(i.Magnitude.Value));
387 var dnInsightsSum = values.Where(i => i.Direction ==
InsightDirection.Down).Sum(i => Math.Abs(i.Magnitude.Value));
388 return new { View = values.Key, Q = upInsightsSum > dnInsightsSum ? upInsightsSum : dnInsightsSum };
390 .Where(x => x.Q != 0)
391 .ToDictionary(k => k.View, v => v.Q);
393 var tmpP = insights.GroupBy(insight => insight.SourceModel)
396 var q = tmpQ[values.Key];
397 var results = values.ToDictionary(x => x.Symbol, insight =>
399 var value = (int)insight.Direction * Math.Abs(insight.Magnitude.Value);
403 foreach (var symbol in symbols)
405 if (!results.ContainsKey(symbol))
407 results.Add(symbol, 0d);
410 return new { View = values.Key, Results = results };
412 .Where(r => !r.Results.Select(v => Math.Abs(v.Value)).Sum().IsNaNOrZero())
413 .ToDictionary(k => k.View, v => v.Results);
415 P = Matrix.Create(tmpP.Select(d => d.Value.Values.ToArray()).ToArray());
416 Q = tmpQ.Values.ToArray();
435 private void ApplyBlackLittermanMasterFormula(ref
double[] Π, ref
double[,] Σ,
double[,] P,
double[] Q)
438 var eye = Matrix.Diagonal(Q.GetLength(0), 1);
439 var Ω = Elementwise.Multiply(P.Dot(Σ).DotWithTransposed(P).Multiply(_tau), eye);
440 if (Ω.Determinant() != 0)
443 var Στ = Σ.Multiply(_tau);
444 var A = Στ.DotWithTransposed(P).Dot(P.Dot(Στ).DotWithTransposed(P).Add(Ω).Inverse());
447 Π = Π.Add(A.Dot(Q.Subtract(P.Dot(Π))));
450 var M = Στ.Subtract(A.Dot(P).Dot(Στ));
451 Σ = Σ.Add(M).Multiply(_delta);