chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,243 @@
|
||||
/*
|
||||
* 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 NUnit.Framework;
|
||||
using Python.Runtime;
|
||||
using QuantConnect.Algorithm.Framework.Alphas;
|
||||
using QuantConnect.Algorithm.Framework.Portfolio;
|
||||
using QuantConnect.Data.Market;
|
||||
using QuantConnect.Data.UniverseSelection;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using QuantConnect.Tests.Common.Data.UniverseSelection;
|
||||
|
||||
namespace QuantConnect.Tests.Algorithm.Framework.Portfolio
|
||||
{
|
||||
[TestFixture]
|
||||
public class EqualWeightingPortfolioConstructionModelTests : BaseWeightingPortfolioConstructionModelTests
|
||||
{
|
||||
public override double? Weight => Algorithm.Securities.Count == 0 ? default(double) : 1d / Algorithm.Securities.Count;
|
||||
|
||||
public virtual PortfolioBias PortfolioBias => PortfolioBias.LongShort;
|
||||
|
||||
[OneTimeSetUp]
|
||||
public override void SetUp()
|
||||
{
|
||||
base.SetUp();
|
||||
|
||||
var prices = new Dictionary<Symbol, decimal>
|
||||
{
|
||||
{ Symbol.Create("AIG", SecurityType.Equity, Market.USA), 55.22m },
|
||||
{ Symbol.Create("IBM", SecurityType.Equity, Market.USA), 145.17m },
|
||||
{ Symbol.Create("SPY", SecurityType.Equity, Market.USA), 281.79m },
|
||||
};
|
||||
|
||||
foreach (var kvp in prices)
|
||||
{
|
||||
var symbol = kvp.Key;
|
||||
var security = GetSecurity(symbol);
|
||||
security.SetMarketPrice(new Tick(Algorithm.Time, symbol, kvp.Value, kvp.Value));
|
||||
Algorithm.Securities.Add(symbol, security);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(Language.CSharp, InsightDirection.Up)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Down)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Flat)]
|
||||
[TestCase(Language.Python, InsightDirection.Up)]
|
||||
[TestCase(Language.Python, InsightDirection.Down)]
|
||||
[TestCase(Language.Python, InsightDirection.Flat)]
|
||||
public override void AutomaticallyRemoveInvestedWithNewInsights(Language language, InsightDirection direction)
|
||||
{
|
||||
SetPortfolioConstruction(language);
|
||||
|
||||
if (PortfolioBias != PortfolioBias.LongShort && (int)direction != (int)PortfolioBias)
|
||||
{
|
||||
direction = InsightDirection.Flat;
|
||||
}
|
||||
|
||||
// Let's create a position for SPY
|
||||
var insights = new[] { GetInsight(Symbols.SPY, direction, Algorithm.UtcTime) };
|
||||
|
||||
foreach (var target in Algorithm.PortfolioConstruction.CreateTargets(Algorithm, insights))
|
||||
{
|
||||
var holding = Algorithm.Portfolio[target.Symbol];
|
||||
holding.SetHoldings(holding.Price, target.Quantity);
|
||||
Algorithm.Portfolio.SetCash(StartingCash - holding.HoldingsValue);
|
||||
}
|
||||
SetUtcTime(Algorithm.UtcTime.AddDays(2));
|
||||
|
||||
// Equity will be divided by all securities minus 1, since SPY is already invested and we want to remove it
|
||||
var amount = Algorithm.Portfolio.TotalPortfolioValue / (decimal)(1 / Weight - 1) *
|
||||
(1 - Algorithm.Settings.FreePortfolioValuePercentage);
|
||||
var expectedTargets = Algorithm.Securities.Select(x =>
|
||||
{
|
||||
// Expected target quantity for SPY is zero, since it will be removed
|
||||
var quantity = x.Key.Value == "SPY" ? 0 : (int)direction * Math.Floor(amount / x.Value.Price);
|
||||
return new PortfolioTarget(x.Key, quantity);
|
||||
});
|
||||
|
||||
// Do no include SPY in the insights
|
||||
insights = Algorithm.Securities.Keys.Where(x => x.Value != "SPY")
|
||||
.Select(x => GetInsight(x, direction, Algorithm.UtcTime)).ToArray();
|
||||
|
||||
var actualTargets = Algorithm.PortfolioConstruction.CreateTargets(Algorithm, insights);
|
||||
|
||||
AssertTargets(expectedTargets, actualTargets);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(Language.CSharp, InsightDirection.Up)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Down)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Flat)]
|
||||
[TestCase(Language.Python, InsightDirection.Up)]
|
||||
[TestCase(Language.Python, InsightDirection.Down)]
|
||||
[TestCase(Language.Python, InsightDirection.Flat)]
|
||||
public override void DelistedSecurityEmitsFlatTargetWithNewInsights(Language language, InsightDirection direction)
|
||||
{
|
||||
SetPortfolioConstruction(language);
|
||||
|
||||
if (PortfolioBias != PortfolioBias.LongShort && (int)direction != (int)PortfolioBias)
|
||||
{
|
||||
direction = InsightDirection.Flat;
|
||||
}
|
||||
|
||||
var insights = new[] { GetInsight(Symbols.SPY, InsightDirection.Down, Algorithm.UtcTime) };
|
||||
var targets = Algorithm.PortfolioConstruction.CreateTargets(Algorithm, insights).ToList();
|
||||
Assert.AreEqual(1, targets.Count);
|
||||
|
||||
// Removing SPY should clear the key in the insight collection
|
||||
var changes = SecurityChangesTests.RemovedNonInternal(Algorithm.Securities[Symbols.SPY]);
|
||||
Algorithm.PortfolioConstruction.OnSecuritiesChanged(Algorithm, changes);
|
||||
|
||||
// Equity will be divided by all securities minus 1, since SPY is already invested and we want to remove it
|
||||
var amount = Algorithm.Portfolio.TotalPortfolioValue / (decimal)(1 / Weight - 1) *
|
||||
(1 - Algorithm.Settings.FreePortfolioValuePercentage);
|
||||
|
||||
var expectedTargets = Algorithm.Securities.Select(x =>
|
||||
{
|
||||
// Expected target quantity for SPY is zero, since it will be removed
|
||||
var quantity = x.Key.Value == "SPY" ? 0 : (int)direction * Math.Floor(amount / x.Value.Price);
|
||||
return new PortfolioTarget(x.Key, quantity);
|
||||
});
|
||||
|
||||
// Do no include SPY in the insights
|
||||
insights = Algorithm.Securities.Keys.Where(x => x.Value != "SPY")
|
||||
.Select(x => GetInsight(x, direction, Algorithm.UtcTime)).ToArray();
|
||||
|
||||
// Create target from an empty insights array
|
||||
var actualTargets = Algorithm.PortfolioConstruction.CreateTargets(Algorithm, insights);
|
||||
|
||||
AssertTargets(expectedTargets, actualTargets);
|
||||
}
|
||||
|
||||
[TestCase(Language.CSharp, InsightDirection.Up)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Down)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Flat)]
|
||||
[TestCase(Language.Python, InsightDirection.Up)]
|
||||
[TestCase(Language.Python, InsightDirection.Down)]
|
||||
[TestCase(Language.Python, InsightDirection.Flat)]
|
||||
public override void FlatDirectionNotAccountedToAllocation(Language language, InsightDirection direction)
|
||||
{
|
||||
SetPortfolioConstruction(language);
|
||||
|
||||
if (PortfolioBias != PortfolioBias.LongShort && (int)direction != (int)PortfolioBias)
|
||||
{
|
||||
direction = InsightDirection.Flat;
|
||||
}
|
||||
|
||||
// Equity, minus $1 for fees, will be divided by all securities minus 1, since its insight will have flat direction
|
||||
var amount = (Algorithm.Portfolio.TotalPortfolioValue - 1 * (Algorithm.Securities.Count - 1)) * 1 /
|
||||
(decimal)((1 / Weight) - 1) * (1 - Algorithm.Settings.FreePortfolioValuePercentage);
|
||||
|
||||
var expectedTargets = Algorithm.Securities.Select(x =>
|
||||
{
|
||||
// Expected target quantity for SPY is zero, since its insight will have flat direction
|
||||
var quantity = x.Key.Value == "SPY" ? 0 : (int)direction * Math.Floor(amount / x.Value.Price);
|
||||
return new PortfolioTarget(x.Key, quantity);
|
||||
});
|
||||
|
||||
var insights = Algorithm.Securities.Keys.Select(x =>
|
||||
{
|
||||
// SPY insight direction is flat
|
||||
var actualDirection = x.Value == "SPY" ? InsightDirection.Flat : direction;
|
||||
return GetInsight(x, actualDirection, Algorithm.UtcTime);
|
||||
});
|
||||
var actualTargets = Algorithm.PortfolioConstruction.CreateTargets(Algorithm, insights.ToArray());
|
||||
|
||||
AssertTargets(expectedTargets, actualTargets);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase(Language.CSharp, InsightDirection.Up, 1)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Up, -1)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Down, 1)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Down, -1)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Flat, 1)]
|
||||
[TestCase(Language.CSharp, InsightDirection.Flat, -1)]
|
||||
[TestCase(Language.Python, InsightDirection.Up, 1)]
|
||||
[TestCase(Language.Python, InsightDirection.Up, -1)]
|
||||
[TestCase(Language.Python, InsightDirection.Down, 1)]
|
||||
[TestCase(Language.Python, InsightDirection.Down, -1)]
|
||||
[TestCase(Language.Python, InsightDirection.Flat, 1)]
|
||||
[TestCase(Language.Python, InsightDirection.Flat, -1)]
|
||||
public virtual void InsightsReturnsTargetsConsistentWithDirection(Language language, InsightDirection direction, int weightSign)
|
||||
{
|
||||
SetPortfolioConstruction(language);
|
||||
|
||||
if (PortfolioBias != PortfolioBias.LongShort && (int)direction != (int)PortfolioBias)
|
||||
{
|
||||
direction = InsightDirection.Flat;
|
||||
}
|
||||
// Equity will be divided by all securities
|
||||
var amount = Algorithm.Portfolio.TotalPortfolioValue * (decimal)Weight *
|
||||
(1 - Algorithm.Settings.FreePortfolioValuePercentage);
|
||||
var expectedTargets = Algorithm.Securities
|
||||
.Select(x => new PortfolioTarget(x.Key, (int)direction * Math.Floor(amount / x.Value.Price)));
|
||||
|
||||
var insights = Algorithm.Securities.Keys.Select(x => GetInsight(x, direction, Algorithm.UtcTime, weight: weightSign * Weight));
|
||||
var actualTargets = Algorithm.PortfolioConstruction.CreateTargets(Algorithm, insights.ToArray());
|
||||
|
||||
AssertTargets(expectedTargets, actualTargets);
|
||||
}
|
||||
|
||||
public override Insight GetInsight(Symbol symbol, InsightDirection direction, DateTime generatedTimeUtc, TimeSpan? period = null, double? weight = 0.01)
|
||||
{
|
||||
period ??= TimeSpan.FromDays(1);
|
||||
var insight = Insight.Price(symbol, period.Value, direction, weight: Math.Max(0.01, Algorithm.Securities.Count));
|
||||
insight.GeneratedTimeUtc = generatedTimeUtc;
|
||||
insight.CloseTimeUtc = generatedTimeUtc.Add(period.Value);
|
||||
Algorithm.Insights.Add(insight);
|
||||
return insight;
|
||||
}
|
||||
|
||||
public override IPortfolioConstructionModel GetPortfolioConstructionModel(Language language, dynamic paramenter = null)
|
||||
{
|
||||
if (language == Language.CSharp)
|
||||
{
|
||||
return new EqualWeightingPortfolioConstructionModel(paramenter);
|
||||
}
|
||||
|
||||
using (Py.GIL())
|
||||
{
|
||||
const string name = nameof(EqualWeightingPortfolioConstructionModel);
|
||||
var instance = Py.Import(name).GetAttr(name).Invoke(((object)paramenter).ToPython());
|
||||
return new PortfolioConstructionModelPythonWrapper(instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user