Skip to content

Commit

Permalink
Extends EWPCM Rebalancing Options
Browse files Browse the repository at this point in the history
Adds constructor overloads to `EqualWeightingPortfolioConstructionModel` (`EWPCM`) to allow different rebalancing definitions.

It is possible to define rebalancing period with `Resolution`, `TimeSpan` (`timedelta` for Python) or a `Func<DateTime, DateTime>` (`lambda x: x+timedelta(y)`). The last option lets the model use `Expiry` helper class with the members such as `EndOfWeek` and `EndOfMonth`.
  • Loading branch information
AlexCatarino committed Oct 22, 2019
1 parent 7452667 commit 98a2591
Show file tree
Hide file tree
Showing 9 changed files with 193 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
* limitations under the License.
*/

using System;
using QuantConnect.Algorithm.Framework.Alphas;

namespace QuantConnect.Algorithm.Framework.Portfolio
Expand All @@ -29,6 +30,24 @@ namespace QuantConnect.Algorithm.Framework.Portfolio
/// </summary>
public class ConfidenceWeightedPortfolioConstructionModel : InsightWeightingPortfolioConstructionModel
{
/// <summary>
/// Initialize a new instance of <see cref="InsightWeightingPortfolioConstructionModel"/>
/// </summary>
/// <param name="rebalancingFunc">Rebalancing function</param>
public ConfidenceWeightedPortfolioConstructionModel(Func<DateTime, DateTime> rebalancingFunc)
: base(rebalancingFunc)
{
}

/// <summary>
/// Initialize a new instance of <see cref="InsightWeightingPortfolioConstructionModel"/>
/// </summary>
/// <param name="timeSpan">Rebalancing frequency</param>
public ConfidenceWeightedPortfolioConstructionModel(TimeSpan timeSpan)
: base(timeSpan)
{
}

/// <summary>
/// Initialize a new instance of <see cref="ConfidenceWeightedPortfolioConstructionModel"/>
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ class ConfidenceWeightedPortfolioConstructionModel(InsightWeightingPortfolioCons
percent holdings proportionally so the sum is 1.
It will ignore Insight that have no Insight.Confidence value.'''

def __init__(self, resolution = Resolution.Daily):
def __init__(self, rebalancingParam = Resolution.Daily):
'''Initialize a new instance of ConfidenceWeightedPortfolioConstructionModel
Args:
resolution: Rebalancing frequency'''
super().__init__(resolution)
rebalancingParam: Rebalancing parameter. It can be a function, timedelta or Resolution'''
super().__init__(rebalancingParam)

def ShouldCreateTargetForInsight(self, insight):
'''Method that will determine if the portfolio construction model should create a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,37 @@ namespace QuantConnect.Algorithm.Framework.Portfolio
/// </summary>
public class EqualWeightingPortfolioConstructionModel : PortfolioConstructionModel
{
private readonly Func<DateTime, DateTime> _rebalancingFunc;
private DateTime _rebalancingTime;
private readonly TimeSpan _rebalancingPeriod;
private List<Symbol> _removedSymbols;
private readonly InsightCollection _insightCollection = new InsightCollection();
private DateTime? _nextExpiryTime;

/// <summary>
/// Initialize a new instance of <see cref="EqualWeightingPortfolioConstructionModel"/>
/// </summary>
/// <param name="rebalancingFunc">Rebalancing function</param>
public EqualWeightingPortfolioConstructionModel(Func<DateTime, DateTime> rebalancingFunc)
{
_rebalancingFunc = rebalancingFunc;
}

/// <summary>
/// Initialize a new instance of <see cref="EqualWeightingPortfolioConstructionModel"/>
/// </summary>
/// <param name="timeSpan">Rebalancing frequency</param>
public EqualWeightingPortfolioConstructionModel(TimeSpan timeSpan)
: this(dt => dt.Add(timeSpan))
{
}

/// <summary>
/// Initialize a new instance of <see cref="EqualWeightingPortfolioConstructionModel"/>
/// </summary>
/// <param name="resolution">Rebalancing frequency</param>
public EqualWeightingPortfolioConstructionModel(Resolution resolution = Resolution.Daily)
: this(resolution.ToTimeSpan())
{
_rebalancingPeriod = resolution.ToTimeSpan();
}

/// <summary>
Expand Down Expand Up @@ -140,7 +158,7 @@ group insight.Symbol by insight.Symbol into g
targets.AddRange(expiredTargets);

_nextExpiryTime = _insightCollection.GetNextExpiryTime();
_rebalancingTime = algorithm.UtcTime.Add(_rebalancingPeriod);
_rebalancingTime = _rebalancingFunc(algorithm.UtcTime);

return targets;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,22 @@ class EqualWeightingPortfolioConstructionModel(PortfolioConstructionModel):
For insights of direction InsightDirection.Up, long targets are returned and
for insights of direction InsightDirection.Down, short targets are returned.'''

def __init__(self, resolution = Resolution.Daily):
def __init__(self, rebalancingParam = Resolution.Daily):
'''Initialize a new instance of EqualWeightingPortfolioConstructionModel
Args:
resolution: Rebalancing frequency'''
rebalancingParam: Rebalancing parameter. It can be a function, timedelta or Resolution'''
self.insightCollection = InsightCollection()
self.removedSymbols = []
self.nextExpiryTime = UTCMIN
self.rebalancingTime = UTCMIN
self.rebalancingPeriod = Extensions.ToTimeSpan(resolution)
self.rebalancingFunc = rebalancingParam

# If the argument is an instance if Resolution or Timedelta
# Redefine self.rebalancingFunc
if isinstance(rebalancingParam, int):
self.rebalancingFunc = lambda dt: dt + Extensions.ToTimeSpan(rebalancingParam)
if isinstance(rebalancingParam, timedelta):
self.rebalancingFunc = lambda dt: dt + rebalancingParam

def ShouldCreateTargetForInsight(self, insight):
'''Method that will determine if the portfolio construction model should create a
Expand Down Expand Up @@ -119,7 +126,7 @@ def CreateTargets(self, algorithm, insights):
if self.nextExpiryTime is None:
self.nextExpiryTime = UTCMIN

self.rebalancingTime = algorithm.UtcTime + self.rebalancingPeriod
self.rebalancingTime = self.rebalancingFunc(algorithm.UtcTime)

return targets

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
* limitations under the License.
*/

using System;
using System.Collections.Generic;
using System.Linq;
using QuantConnect.Algorithm.Framework.Alphas;
Expand All @@ -31,6 +32,24 @@ namespace QuantConnect.Algorithm.Framework.Portfolio
/// </summary>
public class InsightWeightingPortfolioConstructionModel : EqualWeightingPortfolioConstructionModel
{
/// <summary>
/// Initialize a new instance of <see cref="InsightWeightingPortfolioConstructionModel"/>
/// </summary>
/// <param name="rebalancingFunc">Rebalancing function</param>
public InsightWeightingPortfolioConstructionModel(Func<DateTime, DateTime> rebalancingFunc)
: base(rebalancingFunc)
{
}

/// <summary>
/// Initialize a new instance of <see cref="InsightWeightingPortfolioConstructionModel"/>
/// </summary>
/// <param name="timeSpan">Rebalancing frequency</param>
public InsightWeightingPortfolioConstructionModel(TimeSpan timeSpan)
: base(timeSpan)
{
}

/// <summary>
/// Initialize a new instance of <see cref="InsightWeightingPortfolioConstructionModel"/>
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,11 @@ class InsightWeightingPortfolioConstructionModel(EqualWeightingPortfolioConstruc
percent holdings proportionally so the sum is 1.
It will ignore Insight that have no Insight.Weight value.'''

def __init__(self, resolution = Resolution.Daily):
def __init__(self, rebalancingParam = Resolution.Daily):
'''Initialize a new instance of InsightWeightingPortfolioConstructionModel
Args:
resolution: Rebalancing frequency'''
super().__init__(resolution)
rebalancingParam: Rebalancing parameter. It can be a function, timedelta or Resolution'''
super().__init__(rebalancingParam)

def ShouldCreateTargetForInsight(self, insight):
'''Method that will determine if the portfolio construction model should create a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,15 @@ public void GeneratesZeroTargetForZeroInsightConfidence(Language language)
AssertTargets(actualTargets, new[] {new PortfolioTarget(Symbols.SPY, 0)});
}

[Test]
[TestCase(Language.CSharp)]
[TestCase(Language.Python)]
public void DoesNotThrowWithAlternativeOverloads(Language language)
{
Assert.DoesNotThrow(() => SetPortfolioConstruction(language, _algorithm, TimeSpan.FromDays(1)));
Assert.DoesNotThrow(() => SetPortfolioConstruction(language, _algorithm, Expiry.EndOfWeek));
}

private Security GetSecurity(Symbol symbol)
{
var config = SecurityExchangeHours.AlwaysOpen(DateTimeZone.Utc);
Expand Down Expand Up @@ -401,6 +410,36 @@ private void SetPortfolioConstruction(Language language, QCAlgorithm algorithm)
algorithm.PortfolioConstruction.OnSecuritiesChanged(_algorithm, changes);
}

private void SetPortfolioConstruction(Language language, QCAlgorithm algorithm, TimeSpan span)
{
algorithm.SetPortfolioConstruction(new ConfidenceWeightedPortfolioConstructionModel(span));
if (language == Language.Python)
{
using (Py.GIL())
{
var name = nameof(ConfidenceWeightedPortfolioConstructionModel);
var instance = Py.Import(name).GetAttr(name).Invoke(span.ToPython());
var model = new PortfolioConstructionModelPythonWrapper(instance);
algorithm.SetPortfolioConstruction(model);
}
}
}

private void SetPortfolioConstruction(Language language, QCAlgorithm algorithm, Func<DateTime, DateTime> func)
{
algorithm.SetPortfolioConstruction(new ConfidenceWeightedPortfolioConstructionModel(func));
if (language == Language.Python)
{
using (Py.GIL())
{
var name = nameof(ConfidenceWeightedPortfolioConstructionModel);
var instance = Py.Import(name).GetAttr(name).Invoke(func.ToPython());
var model = new PortfolioConstructionModelPythonWrapper(instance);
algorithm.SetPortfolioConstruction(model);
}
}
}

private void SetUtcTime(DateTime dateTime)
{
_algorithm.SetDateTime(dateTime.ConvertToUtc(_algorithm.TimeZone));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,14 @@ public void DoesNotReturnTargetsIfSecurityPriceIsZero(Language language)
Assert.AreEqual(0, actualTargets.Count());
}

[Test]
[TestCase(Language.CSharp)]
[TestCase(Language.Python)]
public void DoesNotThrowWithAlternativeOverloads(Language language)
{
Assert.DoesNotThrow(() => SetPortfolioConstruction(language, _algorithm, TimeSpan.FromDays(1)));
Assert.DoesNotThrow(() => SetPortfolioConstruction(language, _algorithm, Expiry.EndOfWeek));
}

private Security GetSecurity(Symbol symbol)
{
Expand Down Expand Up @@ -374,6 +382,36 @@ private void SetPortfolioConstruction(Language language, QCAlgorithm algorithm)
algorithm.PortfolioConstruction.OnSecuritiesChanged(_algorithm, changes);
}

private void SetPortfolioConstruction(Language language, QCAlgorithm algorithm, TimeSpan span)
{
algorithm.SetPortfolioConstruction(new EqualWeightingPortfolioConstructionModel(span));
if (language == Language.Python)
{
using (Py.GIL())
{
var name = nameof(EqualWeightingPortfolioConstructionModel);
var instance = Py.Import(name).GetAttr(name).Invoke(span.ToPython());
var model = new PortfolioConstructionModelPythonWrapper(instance);
algorithm.SetPortfolioConstruction(model);
}
}
}

private void SetPortfolioConstruction(Language language, QCAlgorithm algorithm, Func<DateTime, DateTime> func)
{
algorithm.SetPortfolioConstruction(new EqualWeightingPortfolioConstructionModel(func));
if (language == Language.Python)
{
using (Py.GIL())
{
var name = nameof(EqualWeightingPortfolioConstructionModel);
var instance = Py.Import(name).GetAttr(name).Invoke(func.ToPython());
var model = new PortfolioConstructionModelPythonWrapper(instance);
algorithm.SetPortfolioConstruction(model);
}
}
}

private void SetUtcTime(DateTime dateTime)
{
_algorithm.SetDateTime(dateTime.ConvertToUtc(_algorithm.TimeZone));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,15 @@ public void GeneratesZeroTargetForZeroInsightWeight(Language language)
AssertTargets(actualTargets, new[] {new PortfolioTarget(Symbols.SPY, 0)});
}

[Test]
[TestCase(Language.CSharp)]
[TestCase(Language.Python)]
public void DoesNotThrowWithAlternativeOverloads(Language language)
{
Assert.DoesNotThrow(() => SetPortfolioConstruction(language, _algorithm, TimeSpan.FromDays(1)));
Assert.DoesNotThrow(() => SetPortfolioConstruction(language, _algorithm, Expiry.EndOfWeek));
}

private Security GetSecurity(Symbol symbol)
{
var config = SecurityExchangeHours.AlwaysOpen(DateTimeZone.Utc);
Expand Down Expand Up @@ -401,6 +410,37 @@ private void SetPortfolioConstruction(Language language, QCAlgorithm algorithm)
algorithm.PortfolioConstruction.OnSecuritiesChanged(_algorithm, changes);
}


private void SetPortfolioConstruction(Language language, QCAlgorithm algorithm, TimeSpan span)
{
algorithm.SetPortfolioConstruction(new InsightWeightingPortfolioConstructionModel(span));
if (language == Language.Python)
{
using (Py.GIL())
{
var name = nameof(InsightWeightingPortfolioConstructionModel);
var instance = Py.Import(name).GetAttr(name).Invoke(span.ToPython());
var model = new PortfolioConstructionModelPythonWrapper(instance);
algorithm.SetPortfolioConstruction(model);
}
}
}

private void SetPortfolioConstruction(Language language, QCAlgorithm algorithm, Func<DateTime, DateTime> func)
{
algorithm.SetPortfolioConstruction(new InsightWeightingPortfolioConstructionModel(func));
if (language == Language.Python)
{
using (Py.GIL())
{
var name = nameof(InsightWeightingPortfolioConstructionModel);
var instance = Py.Import(name).GetAttr(name).Invoke(func.ToPython());
var model = new PortfolioConstructionModelPythonWrapper(instance);
algorithm.SetPortfolioConstruction(model);
}
}
}

private void SetUtcTime(DateTime dateTime)
{
_algorithm.SetDateTime(dateTime.ConvertToUtc(_algorithm.TimeZone));
Expand Down

0 comments on commit 98a2591

Please sign in to comment.