Skip to content

Commit

Permalink
Merge pull request QuantConnect#3718 from AlexCatarino/feature-3672-c…
Browse files Browse the repository at this point in the history
…onfidence-weighted-pcm

Implements ConfidenceWeightedPortfolioConstructionModel
  • Loading branch information
Martin-Molinero authored Oct 18, 2019
2 parents 36ce55e + 1e2cec3 commit d95967e
Show file tree
Hide file tree
Showing 11 changed files with 730 additions and 10 deletions.
111 changes: 111 additions & 0 deletions Algorithm.CSharp/ConfidenceWeightedFrameworkAlgorithm.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*
* 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 System;
using System.Collections.Generic;
using QuantConnect.Algorithm.Framework.Alphas;
using QuantConnect.Algorithm.Framework.Execution;
using QuantConnect.Algorithm.Framework.Portfolio;
using QuantConnect.Algorithm.Framework.Selection;
using QuantConnect.Interfaces;

namespace QuantConnect.Algorithm.CSharp
{
/// <summary>
/// Test algorithm using <see cref="ConfidenceWeightedPortfolioConstructionModel"/> and <see cref="ConstantAlphaModel"/>
/// generating a constant <see cref="Insight"/> with a 0.25 confidence
/// </summary>
public class ConfidenceWeightedFrameworkAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition
{
/// <summary>
/// Initialise the data and resolution required, as well as the cash and start-end dates for your algorithm. All algorithms must initialized.
/// </summary>
public override void Initialize()
{
// Set requested data resolution
UniverseSettings.Resolution = Resolution.Minute;

SetStartDate(2013, 10, 07); //Set Start Date
SetEndDate(2013, 10, 11); //Set End Date
SetCash(100000); //Set Strategy Cash

// set algorithm framework models
SetUniverseSelection(new ManualUniverseSelectionModel(QuantConnect.Symbol.Create("SPY", SecurityType.Equity, Market.USA)));
SetAlpha(new ConstantAlphaModel(InsightType.Price, InsightDirection.Up, TimeSpan.FromMinutes(20), 0.025, 0.25));
SetPortfolioConstruction(new ConfidenceWeightedPortfolioConstructionModel());
SetExecution(new ImmediateExecutionModel());
}

public override void OnEndOfAlgorithm()
{
if (// holdings value should be 0.25 - to avoid price fluctuation issue we compare with 0.28 and 0.23
Portfolio.TotalHoldingsValue > Portfolio.TotalPortfolioValue * 0.28m
||
Portfolio.TotalHoldingsValue < Portfolio.TotalPortfolioValue * 0.23m)
{
throw new Exception($"Unexpected Total Holdings Value: {Portfolio.TotalHoldingsValue}");
}
}

/// <summary>
/// This is used by the regression test system to indicate if the open source Lean repository has the required data to run this algorithm.
/// </summary>
public bool CanRunLocally { get; } = true;

/// <summary>
/// This is used by the regression test system to indicate which languages this algorithm is written in.
/// </summary>
public Language[] Languages { get; } = { Language.CSharp, Language.Python };

/// <summary>
/// This is used by the regression test system to indicate what the expected statistics are from running the algorithm
/// </summary>
public Dictionary<string, string> ExpectedStatistics => new Dictionary<string, string>
{
{"Total Trades", "6"},
{"Average Win", "0.00%"},
{"Average Loss", "0.00%"},
{"Compounding Annual Return", "34.982%"},
{"Drawdown", "0.600%"},
{"Expectancy", "-0.495"},
{"Net Profit", "0.412%"},
{"Sharpe Ratio", "4.016"},
{"Loss Rate", "67%"},
{"Win Rate", "33%"},
{"Profit-Loss Ratio", "0.52"},
{"Alpha", "0.146"},
{"Beta", "0.077"},
{"Annual Standard Deviation", "0.043"},
{"Annual Variance", "0.002"},
{"Information Ratio", "-1.027"},
{"Tracking Error", "0.179"},
{"Treynor Ratio", "2.239"},
{"Total Fees", "$6.00"},
{"Total Insights Generated", "100"},
{"Total Insights Closed", "99"},
{"Total Insights Analysis Completed", "99"},
{"Long Insight Count", "100"},
{"Short Insight Count", "0"},
{"Long/Short Ratio", "100%"},
{"Estimated Monthly Alpha Value", "$148197.8440"},
{"Total Accumulated Estimated Alpha Value", "$25522.9620"},
{"Mean Population Estimated Insight Value", "$257.8077"},
{"Mean Population Direction", "54.5455%"},
{"Mean Population Magnitude", "54.5455%"},
{"Rolling Averaged Population Direction", "59.8056%"},
{"Rolling Averaged Population Magnitude", "59.8056%"}
};
}
}
1 change: 1 addition & 0 deletions Algorithm.CSharp/QuantConnect.Algorithm.CSharp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@
<Compile Include="CustomDataAddDataCoarseSelectionRegressionAlgorithm.cs" />
<Compile Include="CustomDataAddDataRegressionAlgorithm.cs" />
<Compile Include="DynamicSecurityDataAlgorithm.cs" />
<Compile Include="ConfidenceWeightedFrameworkAlgorithm.cs" />
<Compile Include="SmartInsiderDataAlgorithm.cs" />
<Compile Include="BasicTemplateAlgorithm.cs" />
<Compile Include="Benchmarks\StatefulCoarseUniverseSelectionBenchmark.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* 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 QuantConnect.Algorithm.Framework.Alphas;

namespace QuantConnect.Algorithm.Framework.Portfolio
{
/// <summary>
/// Provides an implementation of <see cref="IPortfolioConstructionModel"/> that generates percent targets based on the
/// <see cref="Insight.Confidence"/>. The target percent holdings of each Symbol is given by the <see cref="Insight.Confidence"/>
/// from the last active <see cref="Insight"/> for that symbol.
/// For insights of direction <see cref="InsightDirection.Up"/>, long targets are returned and for insights of direction
/// <see cref="InsightDirection.Down"/>, short targets are returned.
/// If the sum of all the last active <see cref="Insight"/> per symbol is bigger than 1, it will factor down each target
/// percent holdings proportionally so the sum is 1.
/// It will ignore <see cref="Insight"/> that have no <see cref="Insight.Confidence"/> value.
/// </summary>
public class ConfidenceWeightedPortfolioConstructionModel : InsightWeightingPortfolioConstructionModel
{
/// <summary>
/// Initialize a new instance of <see cref="ConfidenceWeightedPortfolioConstructionModel"/>
/// </summary>
/// <param name="resolution">Rebalancing frequency</param>
public ConfidenceWeightedPortfolioConstructionModel(Resolution resolution = Resolution.Daily)
: base(resolution)
{
}

/// <summary>
/// Method that will determine if the portfolio construction model should create a
/// target for this insight
/// </summary>
/// <param name="insight">The insight to create a target for</param>
/// <returns>True if the portfolio should create a target for the insight</returns>
public override bool ShouldCreateTargetForInsight(Insight insight)
{
return insight.Confidence.HasValue;
}

/// <summary>
/// Method that will determine which member will be used to compute the weights and gets its value
/// </summary>
/// <param name="insight">The insight to create a target for</param>
/// <returns>The value of the selected insight member</returns>
protected override double GetValue(Insight insight) => insight.Confidence ?? 0;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# 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.

from clr import AddReference
AddReference("QuantConnect.Common")
AddReference("QuantConnect.Algorithm.Framework")

from QuantConnect import Resolution
from InsightWeightingPortfolioConstructionModel import InsightWeightingPortfolioConstructionModel

class ConfidenceWeightedPortfolioConstructionModel(InsightWeightingPortfolioConstructionModel):
'''Provides an implementation of IPortfolioConstructionModel that generates percent targets based on the
Insight.Confidence. The target percent holdings of each Symbol is given by the Insight.Confidence from the last
active Insight for that symbol.
For insights of direction InsightDirection.Up, long targets are returned and for insights of direction
InsightDirection.Down, short targets are returned.
If the sum of all the last active Insight per symbol is bigger than 1, it will factor down each target
percent holdings proportionally so the sum is 1.
It will ignore Insight that have no Insight.Confidence value.'''

def __init__(self, resolution = Resolution.Daily):
'''Initialize a new instance of ConfidenceWeightedPortfolioConstructionModel
Args:
resolution: Rebalancing frequency'''
super().__init__(resolution)

def ShouldCreateTargetForInsight(self, insight):
'''Method that will determine if the portfolio construction model should create a
target for this insight
Args:
insight: The insight to create a target for'''
# Ignore insights that don't have Confidence value
return insight.Confidence is not None

def GetValue(self, insight):
'''Method that will determine which member will be used to compute the weights and gets its value
Args:
insight: The insight to create a target for
Returns:
The value of the selected insight member'''
return insight.Confidence
Original file line number Diff line number Diff line change
Expand Up @@ -60,17 +60,24 @@ public override Dictionary<Insight, double> DetermineTargetPercent(ICollection<I
{
var result = new Dictionary<Insight, double>();
// We will adjust weights proportionally in case the sum is > 1 so it sums to 1.
var weightSums = activeInsights.Sum(insight => insight.Weight.Value);
var weightSums = activeInsights.Sum(insight => GetValue(insight));
var weightFactor = 1.0;
if (weightSums > 1)
{
weightFactor = 1 / weightSums;
}
foreach (var insight in activeInsights)
{
result[insight] = (int)insight.Direction * insight.Weight.Value * weightFactor;
result[insight] = (int)insight.Direction * GetValue(insight) * weightFactor;
}
return result;
}

/// <summary>
/// Method that will determine which member will be used to compute the weights and gets its value
/// </summary>
/// <param name="insight">The insight to create a target for</param>
/// <returns>The value of the selected insight member</returns>
protected virtual double GetValue(Insight insight) => insight.Weight ?? 0;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,8 @@
AddReference("QuantConnect.Common")
AddReference("QuantConnect.Algorithm.Framework")

from QuantConnect import Resolution, Extensions
from QuantConnect import Resolution
from QuantConnect.Algorithm.Framework.Alphas import *
from itertools import groupby
from datetime import datetime, timedelta
from pytz import utc
UTCMIN = datetime.min.replace(tzinfo=utc)
from EqualWeightingPortfolioConstructionModel import EqualWeightingPortfolioConstructionModel

class InsightWeightingPortfolioConstructionModel(EqualWeightingPortfolioConstructionModel):
Expand Down Expand Up @@ -53,10 +49,18 @@ def DetermineTargetPercent(self, activeInsights):
activeInsights: The active insights to generate a target for'''
result = {}
# We will adjust weights proportionally in case the sum is > 1 so it sums to 1.
weightSums = sum(insight.Weight for insight in activeInsights)
weightSums = sum(self.GetValue(insight) for insight in activeInsights)
weightFactor = 1.0
if weightSums > 1:
weightFactor = 1 / weightSums
for insight in activeInsights:
result[insight] = insight.Direction * insight.Weight * weightFactor
return result
result[insight] = insight.Direction * self.GetValue(insight) * weightFactor
return result

def GetValue(self, insight):
'''Method that will determine which member will be used to compute the weights and gets its value
Args:
insight: The insight to create a target for
Returns:
The value of the selected insight member'''
return insight.Weight
4 changes: 4 additions & 0 deletions Algorithm.Framework/QuantConnect.Algorithm.Framework.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@
<Compile Include="Execution\VolumeWeightedAveragePriceExecutionModel.cs" />
<Compile Include="NotifiedSecurityChanges.cs" />
<Compile Include="Portfolio\BlackLittermanOptimizationPortfolioConstructionModel.cs" />
<Compile Include="Portfolio\ConfidenceWeightedPortfolioConstructionModel.cs" />
<Compile Include="Portfolio\InsightWeightingPortfolioConstructionModel.cs" />
<Compile Include="Portfolio\UnconstrainedMeanVariancePortfolioOptimizer.cs" />
<Compile Include="Portfolio\MaximumSharpeRatioPortfolioOptimizer.cs" />
Expand Down Expand Up @@ -175,6 +176,9 @@
<Content Include="Portfolio\BlackLittermanOptimizationPortfolioConstructionModel.py">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Portfolio\ConfidenceWeightedPortfolioConstructionModel.py">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="Portfolio\InsightWeightingPortfolioConstructionModel.py">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
Expand Down
59 changes: 59 additions & 0 deletions Algorithm.Python/ConfidenceWeightedFrameworkAlgorithm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# 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.

from clr import AddReference
AddReference("System")
AddReference("QuantConnect.Algorithm")
AddReference("QuantConnect.Algorithm.Framework")
AddReference("QuantConnect.Common")

from System import *
from QuantConnect import *
from QuantConnect.Orders import *
from QuantConnect.Algorithm import *
from QuantConnect.Algorithm.Framework import *
from QuantConnect.Algorithm.Framework.Alphas import *
from QuantConnect.Algorithm.Framework.Execution import *
from QuantConnect.Algorithm.Framework.Portfolio import *
from QuantConnect.Algorithm.Framework.Risk import *
from QuantConnect.Algorithm.Framework.Selection import *
from datetime import timedelta

### <summary>
### Test algorithm using 'ConfidenceWeightedPortfolioConstructionModel' and 'ConstantAlphaModel'
### generating a constant 'Insight' with a 0.25 confidence
### </summary>
class ConfidenceWeightedFrameworkAlgorithm(QCAlgorithm):
def Initialize(self):
''' Initialise the data and resolution required, as well as the cash and start-end dates for your algorithm. All algorithms must initialized.'''

# Set requested data resolution
self.UniverseSettings.Resolution = Resolution.Minute

self.SetStartDate(2013,10,7) #Set Start Date
self.SetEndDate(2013,10,11) #Set End Date
self.SetCash(100000) #Set Strategy Cash

symbols = [ Symbol.Create("SPY", SecurityType.Equity, Market.USA) ]

# set algorithm framework models
self.SetUniverseSelection(ManualUniverseSelectionModel(symbols))
self.SetAlpha(ConstantAlphaModel(InsightType.Price, InsightDirection.Up, timedelta(minutes = 20), 0.025, 0.25))
self.SetPortfolioConstruction(ConfidenceWeightedPortfolioConstructionModel())
self.SetExecution(ImmediateExecutionModel())

def OnEndOfAlgorithm(self):
# holdings value should be 0.25 - to avoid price fluctuation issue we compare with 0.28 and 0.23
if (self.Portfolio.TotalHoldingsValue > self.Portfolio.TotalPortfolioValue * 0.28
or self.Portfolio.TotalHoldingsValue < self.Portfolio.TotalPortfolioValue * 0.23):
raise ValueError("Unexpected Total Holdings Value: " + str(self.Portfolio.TotalHoldingsValue))
1 change: 1 addition & 0 deletions Algorithm.Python/QuantConnect.Algorithm.Python.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
<Content Include="CustomDataAddDataOnSecuritiesChangedRegressionAlgorithm.py" />
<Content Include="CustomDataAddDataCoarseSelectionRegressionAlgorithm.py" />
<Content Include="DynamicSecurityDataAlgorithm.py" />
<Content Include="ConfidenceWeightedFrameworkAlgorithm.py" />
<Content Include="KerasNeuralNetworkAlgorithm.py" />
<None Include="PsychSignalSentimentRegressionAlgorithm.py" />
<Content Include="CustomDataUsingMapFileRegressionAlgorithm.py" />
Expand Down
Loading

0 comments on commit d95967e

Please sign in to comment.