Compare commits

...

8 Commits
16632 ... 12423

Author SHA1 Message Date
Martin-Molinero
02ee69826b Add alpha license to Organization response 2021-07-26 20:09:12 -03:00
Martin-Molinero
2a5c939266 Protobuf AlphaStreamsPortfolioState
- Protobuf AlphaStreamsPortfolioState. Adding unit tests
- Add variable TPV tests for EW ASPCM
2021-07-26 17:11:49 -03:00
Martin-Molinero
7220895900 Add unit tests for EW AS PCM and fixing bugs 2021-07-26 12:52:24 -03:00
Martin-Molinero
f76de4b738 Fix regression tests 2021-07-26 12:52:24 -03:00
Martin-Molinero
b659c7a2f1 Improvements on AlphaStreams algorithm 2021-07-26 12:52:24 -03:00
Martin-Molinero
cdeb2e1fcb Convert AlphaStreamsPortfolio to data source 2021-07-26 12:52:24 -03:00
Martin-Molinero
442fbd3012 Rename 2021-07-26 12:52:23 -03:00
Martin-Molinero
1ee6db8b60 Alpha holdings state
- Alpha result packet will optionally provide the algorithms portfolio
  state
2021-07-26 12:52:23 -03:00
18 changed files with 1069 additions and 42 deletions

View File

@@ -0,0 +1,158 @@
/*
* 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.Linq;
using QuantConnect.Data;
using QuantConnect.Orders;
using QuantConnect.Interfaces;
using System.Collections.Generic;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Data.Custom.AlphaStreams;
using QuantConnect.Algorithm.Framework.Execution;
using QuantConnect.Algorithm.Framework.Portfolio;
namespace QuantConnect.Algorithm.CSharp
{
/// <summary>
/// Example algorithm consuming an alpha streams portfolio state and trading based on it
/// </summary>
public class AlphaStreamsBasicTemplateAlgorithm : QCAlgorithm, IRegressionAlgorithmDefinition
{
private Dictionary<Symbol, HashSet<Symbol>> _symbolsPerAlpha;
/// <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()
{
SetStartDate(2018, 04, 04);
SetEndDate(2018, 04, 06);
SetExecution(new ImmediateExecutionModel());
Settings.MinimumOrderMarginPortfolioPercentage = 0.01m;
_symbolsPerAlpha = new Dictionary<Symbol, HashSet<Symbol>>();
SetPortfolioConstruction(new EqualWeightingAlphaStreamsPortfolioConstructionModel());
foreach (var alphaId in new [] { "623b06b231eb1cc1aa3643a46", "9fc8ef73792331b11dbd5429a" })
{
var alpha = AddData<AlphaStreamsPortfolioState>(alphaId);
_symbolsPerAlpha[alpha.Symbol] = new HashSet<Symbol>();
}
}
/// <summary>
/// OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here.
/// </summary>
/// <param name="data">Slice object keyed by symbol containing the stock data</param>
public override void OnData(Slice data)
{
foreach (var portfolioState in data.Get<AlphaStreamsPortfolioState>().Values)
{
var alphaId = portfolioState.Symbol;
var currentSymbols = _symbolsPerAlpha[alphaId];
var newSymbols = new HashSet<Symbol>(currentSymbols.Count);
foreach (var symbol in portfolioState.PositionGroups?.SelectMany(positionGroup => positionGroup.Positions).Select(state => state.Symbol) ?? Enumerable.Empty<Symbol>())
{
// only add it if it's not used by any alpha (already added check)
if (newSymbols.Add(symbol) && !UsedBySomeAlpha(symbol))
{
AddSecurity(symbol);
}
}
_symbolsPerAlpha[alphaId] = newSymbols;
foreach (var symbol in currentSymbols.Where(symbol => !UsedBySomeAlpha(symbol)))
{
RemoveSecurity(symbol);
}
}
}
public override void OnOrderEvent(OrderEvent orderEvent)
{
Debug($"OnOrderEvent: {orderEvent}");
}
public override void OnSecuritiesChanged(SecurityChanges changes)
{
Debug($"OnSecuritiesChanged: {changes}");
}
private bool UsedBySomeAlpha(Symbol asset)
{
return _symbolsPerAlpha.Any(pair => pair.Value.Contains(asset));
}
/// <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 };
/// <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", "2"},
{"Average Win", "0%"},
{"Average Loss", "-0.12%"},
{"Compounding Annual Return", "-14.756%"},
{"Drawdown", "0.200%"},
{"Expectancy", "-1"},
{"Net Profit", "-0.117%"},
{"Sharpe Ratio", "0"},
{"Probabilistic Sharpe Ratio", "0%"},
{"Loss Rate", "100%"},
{"Win Rate", "0%"},
{"Profit-Loss Ratio", "0"},
{"Alpha", "0"},
{"Beta", "0"},
{"Annual Standard Deviation", "0"},
{"Annual Variance", "0"},
{"Information Ratio", "2.474"},
{"Tracking Error", "0.339"},
{"Treynor Ratio", "0"},
{"Total Fees", "$0.00"},
{"Estimated Strategy Capacity", "$83000.00"},
{"Lowest Capacity Asset", "BTCUSD XJ"},
{"Fitness Score", "0.017"},
{"Kelly Criterion Estimate", "0"},
{"Kelly Criterion Probability Value", "0"},
{"Sortino Ratio", "79228162514264337593543950335"},
{"Return Over Maximum Drawdown", "-138.559"},
{"Portfolio Turnover", "0.034"},
{"Total Insights Generated", "0"},
{"Total Insights Closed", "0"},
{"Total Insights Analysis Completed", "0"},
{"Long Insight Count", "0"},
{"Short Insight Count", "0"},
{"Long/Short Ratio", "100%"},
{"Estimated Monthly Alpha Value", "$0"},
{"Total Accumulated Estimated Alpha Value", "$0"},
{"Mean Population Estimated Insight Value", "$0"},
{"Mean Population Direction", "0%"},
{"Mean Population Magnitude", "0%"},
{"Rolling Averaged Population Direction", "0%"},
{"Rolling Averaged Population Magnitude", "0%"},
{"OrderListHash", "d3b6e0db0929e96d23c1cffd394858f1"}
};
}
}

View File

@@ -0,0 +1,206 @@
/*
* 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.Linq;
using System.Collections.Generic;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Data.Custom.AlphaStreams;
using QuantConnect.Algorithm.Framework.Alphas;
namespace QuantConnect.Algorithm.Framework.Portfolio
{
/// <summary>
/// Equal weighting alpha streams portfolio construction model that will generate aggregated security targets taking into account all the alphas positions
/// and an equal weighting factor for each alpha, which is also factored by the relation of the alphas portfolio value and the current algorithms portfolio value,
/// overriding <see cref="GetAlphaWeight"/> allows custom weighting implementations
/// </summary>
public class EqualWeightingAlphaStreamsPortfolioConstructionModel : IPortfolioConstructionModel
{
private bool _rebalance;
private readonly Queue<Symbol> _removedSymbols = new Queue<Symbol>();
private Dictionary<Symbol, decimal> _unitQuantity = new Dictionary<Symbol, decimal>();
private Dictionary<Symbol, PortfolioTarget> _targetsPerSymbol = new Dictionary<Symbol, PortfolioTarget>();
private Dictionary<Symbol, Dictionary<Symbol, PortfolioTarget>> _targetsPerSymbolPerAlpha = new Dictionary<Symbol, Dictionary<Symbol, PortfolioTarget>>();
/// <summary>
/// Access the last portfolio state per alpha
/// </summary>
protected Dictionary<Symbol, AlphaStreamsPortfolioState> LastPortfolioPerAlpha = new Dictionary<Symbol, AlphaStreamsPortfolioState>();
/// <summary>
/// Create portfolio targets from the specified insights
/// </summary>
/// <param name="algorithm">The algorithm instance</param>
/// <param name="insights">The insights to create portfolio targets from</param>
/// <returns>An enumerable of portfolio targets to be sent to the execution model</returns>
public IEnumerable<IPortfolioTarget> CreateTargets(QCAlgorithm algorithm, Insight[] insights)
{
while (_removedSymbols.TryDequeue(out var removedSymbol))
{
yield return new PortfolioTarget(removedSymbol, 0);
}
var updatedTargets = new Dictionary<Symbol, IPortfolioTarget>();
foreach (var portfolioState in algorithm.CurrentSlice?.Get<AlphaStreamsPortfolioState>().Values ?? Enumerable.Empty<AlphaStreamsPortfolioState>())
{
if (!_rebalance)
{
foreach (var portfolioTarget in ProcessPortfolioState(portfolioState, algorithm))
{
updatedTargets[portfolioTarget.Symbol] = portfolioTarget;
}
}
// keep the last state per alpha
LastPortfolioPerAlpha[portfolioState.Symbol] = portfolioState;
}
// if an alpha is removed or added we just rebalance all the targets because the weight changes of each alpha
if (_rebalance)
{
foreach (var portfolioTarget in LastPortfolioPerAlpha.Values.SelectMany(portfolioState => ProcessPortfolioState(portfolioState, algorithm)))
{
updatedTargets[portfolioTarget.Symbol] = portfolioTarget;
}
_rebalance = false;
}
foreach (var portfolioTarget in updatedTargets.Values)
{
yield return portfolioTarget;
}
}
/// <summary>
/// Event fired each time the we add/remove securities from the data feed
/// </summary>
/// <param name="algorithm">The algorithm instance that experienced the change in securities</param>
/// <param name="changes">The security additions and removals from the algorithm</param>
public void OnSecuritiesChanged(QCAlgorithm algorithm, SecurityChanges changes)
{
changes.FilterCustomSecurities = false;
foreach (var security in changes.RemovedSecurities)
{
if (security.Type != SecurityType.Base)
{
_removedSymbols.Enqueue(security.Symbol);
}
else if (IsAlphaStreamsPortfolioState(security.Symbol))
{
_rebalance = true;
_targetsPerSymbolPerAlpha.Remove(security.Symbol);
LastPortfolioPerAlpha.Remove(security.Symbol);
}
}
foreach (var security in changes.AddedSecurities)
{
if (security.Type == SecurityType.Base && IsAlphaStreamsPortfolioState(security.Symbol))
{
_rebalance = true;
_targetsPerSymbolPerAlpha[security.Symbol] = new Dictionary<Symbol, PortfolioTarget>();
}
}
}
/// <summary>
/// Determines the portfolio weight to give a specific alpha. Default implementation just returns equal weighting
/// </summary>
protected virtual decimal GetAlphaWeight(AlphaStreamsPortfolioState portfolioState, QCAlgorithm algorithm)
{
if (portfolioState.TotalPortfolioValue == 0)
{
return 0;
}
var equalWeightFactor = 1m / _targetsPerSymbolPerAlpha.Count;
return (algorithm.Portfolio.TotalPortfolioValue * equalWeightFactor) / portfolioState.TotalPortfolioValue;
}
private IEnumerable<IPortfolioTarget> ProcessPortfolioState(AlphaStreamsPortfolioState portfolioState, QCAlgorithm algorithm)
{
var alphaId = portfolioState.Symbol;
if(!_targetsPerSymbolPerAlpha.TryGetValue(alphaId, out var ourExistingTargets))
{
_targetsPerSymbolPerAlpha[alphaId] = ourExistingTargets = new Dictionary<Symbol, PortfolioTarget>();
}
var alphaWeightFactor = GetAlphaWeight(portfolioState, algorithm);
// first we create all the new aggregated targets for the provided portfolio state
var newTargets = new Dictionary<Symbol, PortfolioTarget>();
foreach (var positionGroup in portfolioState.PositionGroups ?? Enumerable.Empty<PositionGroupState>())
{
foreach (var position in positionGroup.Positions)
{
// let's keep the unit quantity so we can round by it
_unitQuantity[position.Symbol] = position.UnitQuantity;
newTargets.TryGetValue(position.Symbol, out var existingAggregatedTarget);
var quantity = position.Quantity * alphaWeightFactor + (existingAggregatedTarget?.Quantity ?? 0);
newTargets[position.Symbol] = new PortfolioTarget(position.Symbol, quantity.DiscretelyRoundBy(position.UnitQuantity, MidpointRounding.ToZero));
}
}
// We adjust the new targets based on what we already have:
// - We add any existing targets if any -> other alphas
// - But we deduct our own existing target from it if any (previous state)
foreach (var ourNewTarget in newTargets.Values)
{
var symbol = ourNewTarget.Symbol;
var newAggregatedTarget = ourNewTarget;
if (_targetsPerSymbol.TryGetValue(symbol, out var existingAggregatedTarget))
{
ourExistingTargets.TryGetValue(symbol, out var ourExistingTarget);
var quantity = existingAggregatedTarget.Quantity + ourNewTarget.Quantity - (ourExistingTarget?.Quantity ?? 0);
newAggregatedTarget = new PortfolioTarget(symbol, quantity.DiscretelyRoundBy(_unitQuantity[symbol], MidpointRounding.ToZero));
}
ourExistingTargets[symbol] = ourNewTarget;
_targetsPerSymbol[symbol] = newAggregatedTarget;
yield return newAggregatedTarget;
}
// We adjust existing targets for symbols that got removed from this alpha
foreach (var removedTarget in ourExistingTargets.Values.Where(target => !newTargets.ContainsKey(target.Symbol)))
{
var symbol = removedTarget.Symbol;
var newAggregatedTarget = removedTarget;
if (_targetsPerSymbol.TryGetValue(symbol, out var existingAggregatedTarget))
{
var quantity = existingAggregatedTarget.Quantity - removedTarget.Quantity;
newAggregatedTarget = new PortfolioTarget(symbol, quantity.DiscretelyRoundBy(_unitQuantity[symbol], MidpointRounding.ToZero));
}
ourExistingTargets.Remove(symbol);
if (newAggregatedTarget.Quantity != 0)
{
_targetsPerSymbol[symbol] = newAggregatedTarget;
}
else
{
_targetsPerSymbol.Remove(symbol);
}
yield return newAggregatedTarget;
}
}
private static bool IsAlphaStreamsPortfolioState(Symbol symbol)
{
return symbol.ID.Symbol.TryGetCustomDataType(out var type) && type.Equals(nameof(AlphaStreamsPortfolioState), StringComparison.InvariantCultureIgnoreCase);
}
}
}

View File

@@ -692,7 +692,7 @@ namespace QuantConnect.Algorithm
}
else
{
var entry = MarketHoursDatabase.GetEntry(symbol.ID.Market, symbol, symbol.ID.SecurityType);
var entry = MarketHoursDatabase.GetEntry(symbol, new []{ type });
resolution = GetResolution(symbol, resolution);
return SubscriptionManager

View File

@@ -80,6 +80,49 @@ namespace QuantConnect.Api
/// </summary>
[JsonProperty(PropertyName = "credit")]
public Credit Credit { get; set; }
/// <summary>
/// Alphas
/// </summary>
[JsonProperty(PropertyName = "alpha")]
public Alpha Alpha { get; set; }
}
/// <summary>
/// Alphas
/// </summary>
public class Alpha
{
/// <summary>
/// Current licensed alphas
/// </summary>
public List<AlphaLicense> AlphaLicenses { get; set; }
}
/// <summary>
/// An alpha license
/// </summary>
public class AlphaLicense
{
/// <summary>
/// Allocated shares
/// </summary>
public decimal Shares { get; set; }
/// <summary>
/// Gets or sets the alpha id
/// </summary>
public string AlphaId { get; set; }
/// <summary>
/// The start time of this license
/// </summary>
public DateTime Start { get; set; }
/// <summary>
/// The end time of this license
/// </summary>
public DateTime End { get; set; }
}
/// <summary>

View File

@@ -20,6 +20,7 @@ using System.Linq;
using Newtonsoft.Json;
using NodaTime;
using ProtoBuf;
using QuantConnect.Data.Custom.AlphaStreams;
using QuantConnect.Data.Custom.Benzinga;
using QuantConnect.Data.Custom.Estimize;
using QuantConnect.Data.Custom.Tiingo;
@@ -43,6 +44,7 @@ namespace QuantConnect.Data
[ProtoInclude(700, typeof(EstimizeEstimate))]
[ProtoInclude(800, typeof(EstimizeRelease))]
[ProtoInclude(900, typeof(EstimizeConsensus))]
[ProtoInclude(555, typeof(AlphaStreamsPortfolioState))]
public abstract class BaseData : IBaseData
{
private decimal _value;

View File

@@ -0,0 +1,240 @@
/*
* 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 NodaTime;
using ProtoBuf;
using System.IO;
using Newtonsoft.Json;
using QuantConnect.Securities;
using System.Collections.Generic;
using QuantConnect.Securities.Positions;
namespace QuantConnect.Data.Custom.AlphaStreams
{
/// <summary>
/// Snapshot of an algorithms portfolio state
/// </summary>
[ProtoContract(SkipConstructor = true)]
public class AlphaStreamsPortfolioState : BaseData
{
/// <summary>
/// The deployed alpha id. This is the id generated upon submission to the alpha marketplace
/// </summary>
[JsonProperty("alphaId", DefaultValueHandling = DefaultValueHandling.Ignore)]
[ProtoMember(10)]
public string AlphaId { get; set; }
/// <summary>
/// The algorithm's unique deploy identifier
/// </summary>
[JsonProperty("algorithmId", DefaultValueHandling = DefaultValueHandling.Ignore)]
[ProtoMember(11)]
public string AlgorithmId { get; set; }
/// <summary>
/// The source of this data point, 'live trading' or in sample
/// </summary>
[ProtoMember(12)]
public string Source { get; set; }
/// <summary>
/// Portfolio state id
/// </summary>
[ProtoMember(13)]
public int Id { get; set; }
/// <summary>
/// Algorithms account currency
/// </summary>
[ProtoMember(14)]
public string AccountCurrency { get; set; }
/// <summary>
/// The current total portfolio value
/// </summary>
[ProtoMember(15)]
public decimal TotalPortfolioValue { get; set; }
/// <summary>
/// The margin used
/// </summary>
[ProtoMember(16)]
public decimal TotalMarginUsed { get; set; }
/// <summary>
/// The different positions groups
/// </summary>
[JsonProperty("positionGroups", DefaultValueHandling = DefaultValueHandling.Ignore)]
[ProtoMember(17)]
public List<PositionGroupState> PositionGroups { get; set; }
/// <summary>
/// Gets the cash book that keeps track of all currency holdings (only settled cash)
/// </summary>
[JsonProperty("cashBook", DefaultValueHandling = DefaultValueHandling.Ignore)]
[ProtoMember(18)]
public Dictionary<string, Cash> CashBook { get; set; }
/// <summary>
/// Gets the cash book that keeps track of all currency holdings (only unsettled cash)
/// </summary>
[JsonProperty("unsettledCashBook", DefaultValueHandling = DefaultValueHandling.Ignore)]
[ProtoMember(19)]
public Dictionary<string, Cash> UnsettledCashBook { get; set; }
/// <summary>
/// Return the Subscription Data Source
/// </summary>
/// <param name="config">Configuration object</param>
/// <param name="date">Date of this source file</param>
/// <param name="isLiveMode">true if we're in live mode, false for backtesting mode</param>
/// <returns>Subscription Data Source.</returns>
public override SubscriptionDataSource GetSource(SubscriptionDataConfig config, DateTime date, bool isLiveMode)
{
var source = Path.Combine(
Globals.DataFolder,
"alternative",
"alphastreams",
"portfoliostate",
config.Symbol.Value.ToLowerInvariant(),
$"{date:yyyyMMdd}.json"
);
return new SubscriptionDataSource(source, SubscriptionTransportMedium.LocalFile, FileFormat.Csv);
}
/// <summary>
/// Reader converts each line of the data source into BaseData objects.
/// </summary>
/// <param name="config">Subscription data config setup object</param>
/// <param name="line">Content of the source document</param>
/// <param name="date">Date of the requested data</param>
/// <param name="isLiveMode">true if we're in live mode, false for backtesting mode</param>
/// <returns>New data point object</returns>
public override BaseData Reader(SubscriptionDataConfig config, string line, DateTime date, bool isLiveMode)
{
var dataPoint = JsonConvert.DeserializeObject<AlphaStreamsPortfolioState>(line);
dataPoint.Symbol = config.Symbol;
return dataPoint;
}
/// <summary>
/// Specifies the data time zone for this data type
/// </summary>
/// <remarks>Will throw <see cref="InvalidOperationException"/> for security types
/// other than <see cref="SecurityType.Base"/></remarks>
/// <returns>The <see cref="DateTimeZone"/> of this data type</returns>
public override DateTimeZone DataTimeZone()
{
return DateTimeZone.Utc;
}
/// <summary>
/// Return a new instance clone of this object, used in fill forward
/// </summary>
public override BaseData Clone()
{
return new AlphaStreamsPortfolioState
{
Id = Id,
Time = Time,
Source = Source,
Symbol = Symbol,
AlphaId = AlphaId,
DataType = DataType,
CashBook = CashBook,
AlgorithmId = AlgorithmId,
PositionGroups = PositionGroups,
TotalMarginUsed = TotalMarginUsed,
AccountCurrency = AccountCurrency,
UnsettledCashBook = UnsettledCashBook,
TotalPortfolioValue = TotalPortfolioValue,
};
}
/// <summary>
/// Indicates that the data set is expected to be sparse
/// </summary>
public override bool IsSparseData()
{
return true;
}
}
/// <summary>
/// Snapshot of a position group state
/// </summary>
[ProtoContract(SkipConstructor = true)]
public class PositionGroupState
{
/// <summary>
/// Currently margin used
/// </summary>
[ProtoMember(1)]
public decimal MarginUsed { get; set; }
/// <summary>
/// The margin used by this position in relation to the total portfolio value
/// </summary>
[ProtoMember(2)]
public decimal PortfolioValuePercentage { get; set; }
/// <summary>
/// THe positions which compose this group
/// </summary>
[ProtoMember(3)]
public List<PositionState> Positions { get; set; }
}
/// <summary>
/// Snapshot of a position state
/// </summary>
[ProtoContract(SkipConstructor = true)]
public class PositionState : IPosition
{
/// <summary>
/// The symbol
/// </summary>
[ProtoMember(1)]
public Symbol Symbol { get; set; }
/// <summary>
/// The quantity
/// </summary>
[ProtoMember(2)]
public decimal Quantity { get; set; }
/// <summary>
/// The unit quantity. The unit quantities of a group define the group. For example, a covered
/// call has 100 units of stock and -1 units of call contracts.
/// </summary>
[ProtoMember(3)]
public decimal UnitQuantity { get; set; }
/// <summary>
/// Creates a new instance
/// </summary>
public static PositionState Create(IPosition position)
{
return new PositionState
{
Symbol = position.Symbol,
Quantity = position.Quantity,
UnitQuantity = position.UnitQuantity
};
}
}
}

View File

@@ -105,6 +105,50 @@ namespace QuantConnect
return !DateCheck.IsMatch(fileName) && DateTime.Now - TimeSpan.FromDays(DataUpdatePeriod) > File.GetLastWriteTime(filepath);
}
/// <summary>
/// Tries to fetch the custom data type associated with a symbol
/// </summary>
/// <remarks>Custom data type <see cref="SecurityIdentifier"/> symbol value holds their data type</remarks>
public static bool TryGetCustomDataType(this string symbol, out string type)
{
type = null;
if (symbol != null)
{
var index = symbol.LastIndexOf('.');
if (index != -1 && symbol.Length > index + 1)
{
type = symbol.Substring(index + 1);
return true;
}
}
return false;
}
/// <summary>
/// Helper method to get a market hours entry
/// </summary>
/// <param name="marketHoursDatabase">The market hours data base instance</param>
/// <param name="symbol">The symbol to get the entry for</param>
/// <param name="dataTypes">For custom data types can optionally provide data type so that a new entry is added</param>
public static MarketHoursDatabase.Entry GetEntry(this MarketHoursDatabase marketHoursDatabase, Symbol symbol, IEnumerable<Type> dataTypes)
{
if (symbol.SecurityType == SecurityType.Base)
{
if (!marketHoursDatabase.TryGetEntry(symbol.ID.Market, symbol, symbol.ID.SecurityType, out var entry))
{
var type = dataTypes.Single();
var baseInstance = type.GetBaseDataInstance();
baseInstance.Symbol = symbol;
symbol.ID.Symbol.TryGetCustomDataType(out var customType);
// for custom types we will add an entry for that type
entry = marketHoursDatabase.SetEntryAlwaysOpen(symbol.ID.Market, customType != null ? $"TYPE.{customType}" : null, SecurityType.Base, baseInstance.DataTimeZone());
}
return entry;
}
return marketHoursDatabase.GetEntry(symbol.ID.Market, symbol, symbol.ID.SecurityType);
}
/// <summary>
/// Helper method to download a provided url as a string
/// </summary>
@@ -342,6 +386,12 @@ namespace QuantConnect
resultPacket.Orders = resultPacket.Orders.GroupBy(order => order.Id)
.Select(ordersGroup => ordersGroup.Last()).ToList();
}
if (newerPacket.Portfolio != null)
{
// we just keep the newest state if not null
resultPacket.Portfolio = newerPacket.Portfolio;
}
}
}
return resultPacket;

View File

@@ -1,4 +1,4 @@
/*
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
@@ -14,15 +14,11 @@
*
*/
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.2 Copyright 2015 QuantConnect Corporation.
*/
using System.Collections.Generic;
using Newtonsoft.Json;
using QuantConnect.Algorithm.Framework.Alphas;
using QuantConnect.Orders;
using System.Collections.Generic;
using QuantConnect.Data.Custom.AlphaStreams;
using QuantConnect.Algorithm.Framework.Alphas;
namespace QuantConnect.Packets
{
@@ -68,6 +64,12 @@ namespace QuantConnect.Packets
[JsonProperty("orders", DefaultValueHandling = DefaultValueHandling.Ignore)]
public List<Order> Orders { get; set; }
/// <summary>
/// The algorithms current portfolio state
/// </summary>
[JsonProperty("portfolio", DefaultValueHandling = DefaultValueHandling.Ignore)]
public AlphaStreamsPortfolioState Portfolio { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="AlphaResultPacket"/> class
/// </summary>
@@ -84,7 +86,8 @@ namespace QuantConnect.Packets
/// <param name="insights">Alphas generated by the algorithm</param>
/// <param name="orderEvents">OrderEvents generated by the algorithm</param>
/// <param name="orders">Orders generated or updated by the algorithm</param>
public AlphaResultPacket(string algorithmId, int userId, List<Insight> insights = null, List<OrderEvent> orderEvents = null, List<Order> orders = null)
/// <param name="portfolio">The algorithms current portfolio state</param>
public AlphaResultPacket(string algorithmId, int userId, List<Insight> insights = null, List<OrderEvent> orderEvents = null, List<Order> orders = null, AlphaStreamsPortfolioState portfolio = null)
: base(PacketType.AlphaResult)
{
UserId = userId;
@@ -92,6 +95,7 @@ namespace QuantConnect.Packets
Insights = insights;
OrderEvents = orderEvents;
Orders = orders;
Portfolio = portfolio;
}
}
}

View File

@@ -17,6 +17,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using ProtoBuf;
using QuantConnect.Data;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Interfaces;
@@ -29,6 +30,7 @@ namespace QuantConnect.Securities
/// <summary>
/// Represents a holding of a currency in cash.
/// </summary>
[ProtoContract(SkipConstructor = true)]
public class Cash
{
private decimal _conversionRate;
@@ -59,16 +61,19 @@ namespace QuantConnect.Securities
/// <summary>
/// Gets the symbol used to represent this cash
/// </summary>
[ProtoMember(1)]
public string Symbol { get; }
/// <summary>
/// Gets or sets the amount of cash held
/// </summary>
[ProtoMember(2)]
public decimal Amount { get; private set; }
/// <summary>
/// Gets the conversion rate into account currency
/// </summary>
[ProtoMember(3)]
public decimal ConversionRate
{
get
@@ -96,6 +101,7 @@ namespace QuantConnect.Securities
/// <summary>
/// The symbol of the currency, such as $
/// </summary>
[ProtoMember(4)]
public string CurrencySymbol { get; }
/// <summary>
@@ -153,7 +159,8 @@ namespace QuantConnect.Securities
/// <param name="amount">The amount to set the quantity to</param>
public void SetAmount(decimal amount)
{
lock (_locker)
// lock can be null when proto deserializing this instance
lock (_locker ?? new object())
{
Amount = amount;
}

View File

@@ -1,4 +1,4 @@
/*
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
@@ -206,8 +206,7 @@ namespace QuantConnect.Securities
// Some FOPs have the same symbol properties as their futures counterparts.
// So, to save ourselves some space, we can fall back on the existing entries
// so that we don't duplicate the information.
if (!TryGetEntry(market, symbol, securityType, out entry) &&
!(securityType == SecurityType.FutureOption && TryGetEntry(market, FuturesOptionsSymbolMappings.MapFromOption(symbol), SecurityType.Future, out entry)))
if (!TryGetEntry(market, symbol, securityType, out entry))
{
var key = new SecurityDatabaseKey(market, symbol, securityType);
var keys = string.Join(", ", _entries.Keys);
@@ -257,8 +256,14 @@ namespace QuantConnect.Securities
public bool TryGetEntry(string market, string symbol, SecurityType securityType, out Entry entry)
{
return _entries.TryGetValue(new SecurityDatabaseKey(market, symbol, securityType), out entry)
// now check with null symbol key
|| _entries.TryGetValue(new SecurityDatabaseKey(market, null, securityType), out entry);
// now check with null symbol key
|| _entries.TryGetValue(new SecurityDatabaseKey(market, null, securityType), out entry)
// if FOP check for future
|| securityType == SecurityType.FutureOption && TryGetEntry(market,
FuturesOptionsSymbolMappings.MapFromOption(symbol), SecurityType.Future, out entry)
// if custom data type check for type specific entry
|| (securityType == SecurityType.Base && symbol.TryGetCustomDataType(out var customType)
&& _entries.TryGetValue(new SecurityDatabaseKey(market, $"TYPE.{customType}", securityType), out entry));
}
/// <summary>

View File

@@ -79,14 +79,6 @@ namespace QuantConnect.Securities.Positions
return consolidated;
}
/// <summary>
/// Gets the number of lots contained within the specified <paramref name="position"/>
/// </summary>
public static decimal GetNumberOfLots(this IPosition position)
{
return position.Quantity / position.UnitQuantity;
}
/// <summary>
/// Creates a new <see cref="IPosition"/> with quantity equal to <paramref name="numberOfLots"/> times its unit quantity
/// </summary>

View File

@@ -0,0 +1,2 @@
{"AlphaId":"623b06b231eb1cc1aa3643a46","AlgorithmId":"37b0922b-54d0-44bc-8dfb-b90ee4554884","Source":"live trading","AccountCurrency":"USD","TotalPortfolioValue":100000.0,"TotalMarginUsed":1.0,"Time":"2018-04-04T08:03:58.3653852Z","CashBook":{"USD":{"SecuritySymbols":[],"Symbol":"USD","Amount":10.0,"ConversionRate":1.0,"CurrencySymbol":"$","ValueInAccountCurrency":10.0},"EUR":{"SecuritySymbols":[],"Symbol":"EUR","Amount":1.0,"ConversionRate":1.2,"CurrencySymbol":"€","ValueInAccountCurrency":1.2}},"UnsettledCashBook":{"USD":{"SecuritySymbols":[],"Symbol":"USD","Amount":1.0,"ConversionRate":1.0,"CurrencySymbol":"$","ValueInAccountCurrency":1.0}},"PositionGroups":[{"MarginUsed":11.0,"PortfolioValuePercentage":0.1,"Positions":[{"Symbol":{"Value":"BTCUSD","ID":"BTCUSD XJ","Permtick":"BTCUSD"},"Quantity":0.999,"UnitQuantity":0.00000001}]}]}
{"AlphaId":"623b06b231eb1cc1aa3643a46","AlgorithmId":"ba29373c-a1b4-4e45-a587-e31fb02a3557","Source":"live trading","AccountCurrency":"USD","TotalPortfolioValue":100000.0,"TotalMarginUsed":0.0,"Time":"2018-04-04T21:03:58.3782404Z","CashBook":{"USD":{"SecuritySymbols":[],"Symbol":"USD","Amount":10.0,"ConversionRate":1.0,"CurrencySymbol":"$","ValueInAccountCurrency":10.0},"EUR":{"SecuritySymbols":[],"Symbol":"EUR","Amount":1.0,"ConversionRate":1.2,"CurrencySymbol":"€","ValueInAccountCurrency":1.2}},"UnsettledCashBook":{},"PositionGroups":[]}

View File

@@ -0,0 +1,4 @@
{"AlphaId":"9fc8ef73792331b11dbd5429a","AlgorithmId":"ba29373c-a1b4-4e45-429a-e31fb02a3557","Source":"live trading","Id":1,"AccountCurrency":"USD","TotalPortfolioValue":100000,"TotalMarginUsed":112281.0000000,"Time":"2018-04-04T15:03:58.3653852Z","positionGroups":[{"MarginUsed":93567.5000000,"PortfolioValuePercentage":0.4703,"Positions":[{"Symbol":{"Value":"GOOG 160617C00750000","ID":"GOOCV WBGM92QHIYO6|GOOCV VP83T1ZUHROL","Permtick":"GOOG 160617C00750000","Underlying":{"Value":"GOOG","ID":"GOOCV VP83T1ZUHROL","Permtick":"GOOG"}},"Quantity":-5.0,"UnitQuantity":1.0},{"Symbol":{"Value":"GOOG","ID":"GOOCV VP83T1ZUHROL","Permtick":"GOOG"},"Quantity":500.0,"UnitQuantity":1.0}]},{"MarginUsed":18713.5000000,"PortfolioValuePercentage":0.09407,"Positions":[{"Symbol":{"Value":"GOOG","ID":"GOOCV VP83T1ZUHROL","Permtick":"GOOG"},"Quantity":100.0,"UnitQuantity":1.0}]}],"cashBook":{"USD":{"SecuritySymbols":[],"Symbol":"USD","Amount":-222812.25,"ConversionRate":1.0,"CurrencySymbol":"$","ValueInAccountCurrency":-222812.250}}}
{"AlphaId":"9fc8ef73792331b11dbd5429a","AlgorithmId":"ba29373c-a1b4-4e45-429a-e31fb02a3557","Source":"live trading","Id":2,"AccountCurrency":"USD","TotalPortfolioValue":100000,"TotalMarginUsed":18713.5000000,"Time":"2018-04-04T16:03:58.3653852Z","positionGroups":[{"MarginUsed":18713.5000000,"PortfolioValuePercentage":0.09407,"Positions":[{"Symbol":{"Value":"GOOG","ID":"GOOCV VP83T1ZUHROL","Permtick":"GOOG"},"Quantity":100.0,"UnitQuantity":1.0}]}],"cashBook":{"USD":{"SecuritySymbols":[],"Symbol":"USD","Amount":-222812.25,"ConversionRate":1.0,"CurrencySymbol":"$","ValueInAccountCurrency":-222812.250}}}
{"AlphaId":"9fc8ef73792331b11dbd5429a","AlgorithmId":"ba29373c-a1b4-4e45-429a-e31fb02a3557","Source":"live trading","Id":3,"AccountCurrency":"USD","TotalPortfolioValue":100000,"TotalMarginUsed":0,"Time":"2018-04-04T16:33:58.3653852Z","cashBook":{"USD":{"SecuritySymbols":[],"Symbol":"USD","Amount":222812.25,"ConversionRate":1.0,"CurrencySymbol":"$","ValueInAccountCurrency":222812.250}}}
{"AlphaId":"9fc8ef73792331b11dbd5429a","AlgorithmId":"ba29373c-a1b4-4e45-429a-e31fb02a3557","Source":"live trading","Id":4,"AccountCurrency":"USD","TotalPortfolioValue":100000,"TotalMarginUsed":3886.0,"Time":"2018-04-04T16:50:58.3653852Z","positionGroups":[{"MarginUsed":3886.0,"PortfolioValuePercentage":0.01953,"Positions":[{"Symbol":{"Value":"ES19M20 200619C03200000","ID":"ES XFH59UPBIJ7O|ES XFH59UK0MYO1","Permtick":"ES19M20 200619C03200000","Underlying":{"Value":"ES19M20","ID":"ES XFH59UK0MYO1","Permtick":"ES19M20"}},"Quantity":1.0,"UnitQuantity":1.0}]}],"cashBook":{"USD":{"SecuritySymbols":[],"Symbol":"USD","Amount":92585.650,"ConversionRate":1.0,"CurrencySymbol":"$","ValueInAccountCurrency":92585.6500}}}

View File

@@ -458,19 +458,7 @@ namespace QuantConnect.Lean.Engine.DataFeeds
}
}
}
MarketHoursDatabase.Entry marketHoursDbEntry;
if (!_marketHoursDatabase.TryGetEntry(symbol.ID.Market, symbol, symbol.ID.SecurityType, out marketHoursDbEntry))
{
if (symbol.SecurityType == SecurityType.Base)
{
var baseInstance = dataTypes.Single().Item1.GetBaseDataInstance();
baseInstance.Symbol = symbol;
_marketHoursDatabase.SetEntryAlwaysOpen(symbol.ID.Market, null, SecurityType.Base, baseInstance.DataTimeZone());
}
marketHoursDbEntry = _marketHoursDatabase.GetEntry(symbol.ID.Market, symbol, symbol.ID.SecurityType);
}
var marketHoursDbEntry = _marketHoursDatabase.GetEntry(symbol, dataTypes.Select(tuple => tuple.Item1));
var exchangeHours = marketHoursDbEntry.ExchangeHours;
if (symbol.ID.SecurityType.IsOption() ||

View File

@@ -1,4 +1,4 @@
/*
/*
* QUANTCONNECT.COM - Democratizing Finance, Empowering Individuals.
* Lean Algorithmic Trading Engine v2.0. Copyright 2014 QuantConnect Corporation.
*
@@ -14,6 +14,7 @@
*
*/
using System;
using NUnit.Framework;
using QuantConnect.Algorithm;
using QuantConnect.Securities.Cfd;
@@ -23,7 +24,7 @@ using QuantConnect.Securities.Forex;
using QuantConnect.Securities.Future;
using QuantConnect.Securities.Option;
using QuantConnect.Tests.Engine.DataFeeds;
using System;
using QuantConnect.Data.Custom.AlphaStreams;
using Index = QuantConnect.Securities.Index.Index;
namespace QuantConnect.Tests.Algorithm
@@ -108,10 +109,11 @@ namespace QuantConnect.Tests.Algorithm
new TestCaseData(Symbols.SPY_Option_Chain),
new TestCaseData(Symbols.SPY_C_192_Feb19_2016),
new TestCaseData(Symbols.SPY_P_192_Feb19_2016),
new TestCaseData(Symbol.CreateBase(typeof(AlphaStreamsPortfolioState), Symbols.SPY, Market.USA)),
new TestCaseData(Symbol.Create("CustomData", SecurityType.Base, Market.Binance)),
new TestCaseData(Symbol.Create("CustomData2", SecurityType.Base, Market.COMEX))
};
}
}
}
}
}

View File

@@ -0,0 +1,224 @@
/*
* 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.Linq;
using NUnit.Framework;
using QuantConnect.Data;
using QuantConnect.Util;
using QuantConnect.Algorithm;
using System.Collections.Generic;
using QuantConnect.Data.Auxiliary;
using QuantConnect.Lean.Engine.DataFeeds;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Tests.Engine.DataFeeds;
using QuantConnect.Data.Custom.AlphaStreams;
using QuantConnect.Lean.Engine.HistoricalData;
using QuantConnect.Algorithm.Framework.Alphas;
using QuantConnect.Algorithm.Framework.Portfolio;
namespace QuantConnect.Tests.Algorithm.Framework.Portfolio
{
[TestFixture]
public class EqualWeightingAlphaStreamsPortfolioConstructionModelTests
{
private ZipDataCacheProvider _cacheProvider;
private DefaultDataProvider _dataProvider;
private QCAlgorithm _algorithm;
[SetUp]
public virtual void SetUp()
{
_algorithm = new QCAlgorithm();
_dataProvider = new DefaultDataProvider();
var mapFileProvider = new LocalDiskMapFileProvider();
mapFileProvider.Initialize(_dataProvider);
var factorFileProvider = new LocalZipFactorFileProvider();
factorFileProvider.Initialize(mapFileProvider, _dataProvider);
var historyProvider = new SubscriptionDataReaderHistoryProvider();
_cacheProvider = new ZipDataCacheProvider(_dataProvider);
historyProvider.Initialize(new HistoryProviderInitializeParameters(null, null,
_dataProvider, _cacheProvider, mapFileProvider, factorFileProvider,
null, true, new DataPermissionManager()));
_algorithm.SetHistoryProvider(historyProvider);
_algorithm.SubscriptionManager.SetDataManager(new DataManagerStub(_algorithm));
}
[TearDown]
public virtual void TearDown()
{
_cacheProvider.DisposeSafely();
_dataProvider.DisposeSafely();
}
[TestCase(Language.CSharp)]
public void NoTargets(Language language)
{
SetPortfolioConstruction(language);
var targets = _algorithm.PortfolioConstruction.CreateTargets(_algorithm, Array.Empty<Insight>()).ToList();
Assert.AreEqual(0, targets.Count);
}
[TestCase(Language.CSharp)]
public void IgnoresInsights(Language language)
{
SetPortfolioConstruction(language);
var insight = Insight.Price(Symbols.AAPL, Resolution.Minute, 1, InsightDirection.Down, 1, 1, "AlphaId", 0.5);
insight.GeneratedTimeUtc = _algorithm.UtcTime;
insight.CloseTimeUtc = _algorithm.UtcTime.Add(insight.Period);
var targets = _algorithm.PortfolioConstruction.CreateTargets(_algorithm, new[] { insight }).ToList();
Assert.AreEqual(0, targets.Count);
}
[TestCase(Language.CSharp)]
public void SingleAlphaSinglePosition(Language language)
{
SetPortfolioConstruction(language);
var alpha = _algorithm.AddData<AlphaStreamsPortfolioState>("9fc8ef73792331b11dbd5429a").Symbol;
var data = _algorithm.History<AlphaStreamsPortfolioState>(alpha, TimeSpan.FromDays(2)).Last();
_algorithm.SetCurrentSlice(new Slice(_algorithm.UtcTime, new List<BaseData> { data }));
var targets = _algorithm.PortfolioConstruction.CreateTargets(_algorithm, Array.Empty<Insight>()).ToList();
Assert.AreEqual(1, targets.Count);
var position = data.PositionGroups.Single().Positions.Single();
Assert.AreEqual(position.Symbol, targets.Single().Symbol);
Assert.AreEqual(position.Quantity, targets.Single().Quantity);
}
[TestCase(Language.CSharp)]
public void SingleAlphaMultiplePositions(Language language)
{
SetPortfolioConstruction(language);
var alpha = _algorithm.AddData<AlphaStreamsPortfolioState>("9fc8ef73792331b11dbd5429a").Symbol;
var data = _algorithm.History<AlphaStreamsPortfolioState>(alpha, TimeSpan.FromDays(2)).ToList()[0];
_algorithm.SetCurrentSlice(new Slice(_algorithm.UtcTime, new List<BaseData> { data }));
var targets = _algorithm.PortfolioConstruction.CreateTargets(_algorithm, Array.Empty<Insight>()).ToList();
Assert.AreEqual(2, targets.Count);
Assert.AreEqual(600, targets.Single(target => target.Symbol == Symbols.GOOG).Quantity);
var option = Symbol.CreateOption(Symbols.GOOG, Market.USA, OptionStyle.American, OptionRight.Call, 750, new DateTime(2016, 6, 17));
Assert.AreEqual(-5, targets.Single(target => target.Symbol == option).Quantity);
}
[TestCase(Language.CSharp)]
public void SingleAlphaPositionRemoval(Language language)
{
SetPortfolioConstruction(language);
var alpha = _algorithm.AddData<AlphaStreamsPortfolioState>("9fc8ef73792331b11dbd5429a").Symbol;
var data = _algorithm.History<AlphaStreamsPortfolioState>(alpha, TimeSpan.FromDays(2)).Last();
var position = data.PositionGroups.Single().Positions.Single();
_algorithm.SetCurrentSlice(new Slice(_algorithm.UtcTime, new List<BaseData> { data }));
var targets = _algorithm.PortfolioConstruction.CreateTargets(_algorithm, Array.Empty<Insight>()).ToList();
Assert.AreEqual(1, targets.Count);
Assert.AreEqual(position.Symbol, targets.Single().Symbol);
Assert.AreEqual(position.Quantity, targets.Single().Quantity);
_algorithm.SetCurrentSlice(new Slice(_algorithm.UtcTime, new List<BaseData> { new AlphaStreamsPortfolioState { Symbol = alpha } }));
targets = _algorithm.PortfolioConstruction.CreateTargets(_algorithm, Array.Empty<Insight>()).ToList();
Assert.AreEqual(1, targets.Count);
Assert.AreEqual(position.Symbol, targets.Single().Symbol);
Assert.AreEqual(0, targets.Single().Quantity);
// no new targets
targets = _algorithm.PortfolioConstruction.CreateTargets(_algorithm, Array.Empty<Insight>()).ToList();
Assert.AreEqual(0, targets.Count);
}
[TestCase(Language.CSharp, 1000000, 10000)]
[TestCase(Language.CSharp, 10000, 1000000)]
[TestCase(Language.CSharp, 10000, 10000)]
[TestCase(Language.CSharp, 100000, 100000)]
[TestCase(Language.CSharp, 1000000, 1000000)]
public void MultipleAlphaPositionAggregation(Language language, decimal totalPortfolioValueAlpha1, decimal totalPortfolioValueAlpha2)
{
SetPortfolioConstruction(language);
var alpha1 = _algorithm.AddData<AlphaStreamsPortfolioState>("9fc8ef73792331b11dbd5429a");
var alpha2 = _algorithm.AddData<AlphaStreamsPortfolioState>("623b06b231eb1cc1aa3643a46");
_algorithm.OnFrameworkSecuritiesChanged(SecurityChanges.Added(alpha1, alpha2));
var symbol = alpha1.Symbol;
var symbol2 = alpha2.Symbol;
var data = _algorithm.History<AlphaStreamsPortfolioState>(symbol, TimeSpan.FromDays(1)).Last();
data.TotalPortfolioValue = totalPortfolioValueAlpha1;
var position = data.PositionGroups.Single().Positions.Single();
var data2 = (AlphaStreamsPortfolioState)data.Clone();
data2.Symbol = symbol2;
data2.TotalPortfolioValue = totalPortfolioValueAlpha2;
data2.PositionGroups =
new List<PositionGroupState>
{
new PositionGroupState { Positions =
new List<PositionState>
{
new PositionState
{
Quantity = position.Quantity * -10,
Symbol = position.Symbol,
UnitQuantity = 1
}
}}
};
_algorithm.SetCurrentSlice(new Slice(_algorithm.UtcTime, new List<BaseData> { data, data2 }));
var targets = _algorithm.PortfolioConstruction.CreateTargets(_algorithm, Array.Empty<Insight>()).ToList();
Assert.AreEqual(1, targets.Count);
Assert.AreEqual(position.Symbol, targets.Single().Symbol);
var tvpPerAlpha = _algorithm.Portfolio.TotalPortfolioValue * 0.5m;
var alpha1Weight = tvpPerAlpha / data.TotalPortfolioValue;
var alpha2Weight = tvpPerAlpha / data2.TotalPortfolioValue;
Assert.AreEqual((position.Quantity * alpha1Weight).DiscretelyRoundBy(1, MidpointRounding.ToZero)
+ (position.Quantity * -10m * alpha2Weight).DiscretelyRoundBy(1, MidpointRounding.ToZero),
targets.Single().Quantity);
}
private void SetUtcTime(DateTime dateTime) => _algorithm.SetDateTime(dateTime.ConvertToUtc(_algorithm.TimeZone));
private void SetPortfolioConstruction(Language language)
{
_algorithm.SetCurrentSlice(null);
IPortfolioConstructionModel model;
if (language == Language.CSharp)
{
model = new EqualWeightingAlphaStreamsPortfolioConstructionModel();
}
else
{
throw new NotImplementedException($"{language} not implemented");
}
_algorithm.SetPortfolioConstruction(model);
foreach (var kvp in _algorithm.Portfolio)
{
kvp.Value.SetHoldings(kvp.Value.Price, 0);
}
_algorithm.Portfolio.SetCash(100000);
SetUtcTime(new DateTime(2018, 4, 5));
var changes = SecurityChanges.Added(_algorithm.Securities.Values.ToArray());
_algorithm.PortfolioConstruction.OnSecuritiesChanged(_algorithm, changes);
}
}
}

View File

@@ -14,6 +14,7 @@
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
@@ -22,6 +23,7 @@ using Newtonsoft.Json;
using NUnit.Framework;
using ProtoBuf;
using QuantConnect.Data;
using QuantConnect.Data.Custom.AlphaStreams;
using QuantConnect.Data.Custom.Benzinga;
using QuantConnect.Data.Custom.Estimize;
using QuantConnect.Data.Custom.Tiingo;
@@ -626,6 +628,60 @@ namespace QuantConnect.Tests.Common
}
}
[Test]
public void AlphaStreamsPortfolioStateRoundTrip()
{
var symbol = Symbol.CreateBase(typeof(AlphaStreamsPortfolioState),
Symbol.Create("9fc8ef73792331b11dbd5429a", SecurityType.Base, Market.USA),
Market.USA);
var state = new AlphaStreamsPortfolioState
{
Id = 1000,
Time = DateTime.UtcNow,
Symbol = symbol,
Source = "Live trading",
AccountCurrency = Currencies.EUR,
AlgorithmId = "BasicTemplateAlgorithm",
AlphaId = "9fc8ef73792331b11dbd5429a",
CashBook = new Dictionary<string, Cash>
{
{ Currencies.EUR, new Cash(Currencies.EUR, 1, 1)}
},
UnsettledCashBook = new Dictionary<string, Cash>
{
{ Currencies.USD, new Cash(Currencies.USD, 1, 1.2m)}
},
PositionGroups = new List<PositionGroupState>
{
new PositionGroupState
{
MarginUsed = 10,
PortfolioValuePercentage = 0.001m,
Positions = new List<PositionState>
{
new PositionState
{
Quantity = 1,
UnitQuantity = 1,
Symbol = Symbols.SPY
}
}
}
},
TotalMarginUsed = 1000,
TotalPortfolioValue = 100000,
};
var serializedState = state.ProtobufSerialize();
using (var stream = new MemoryStream(serializedState))
{
var result = (AlphaStreamsPortfolioState)Serializer.Deserialize<IEnumerable<BaseData>>(stream).First();
AssertAreEqual(state, result);
}
}
[Test, Ignore("Performance test")]
public void SpeedTest()
{
@@ -698,5 +754,47 @@ namespace QuantConnect.Tests.Common
Log.Trace($"JSON TOOK {end - start}");
}
}
private void AssertAreEqual(object expected, object result)
{
foreach (var propertyInfo in expected.GetType().GetProperties())
{
if (propertyInfo.CustomAttributes.Any(data => data.AttributeType == typeof(ProtoMemberAttribute)))
{
var expectedValue = propertyInfo.GetValue(expected);
var resultValue = propertyInfo.GetValue(result);
if (expectedValue is IList)
{
var expectedValueList = (IList) expectedValue;
var resultValueList = (IList) resultValue;
for (var i = 0; i < expectedValueList.Count; i++)
{
AssertAreEqual(expectedValueList[i], resultValueList[i]);
}
}
else if (expectedValue is IDictionary)
{
var expectedValueDictionary = (IDictionary) expectedValue;
var resultValueDictionary = (IDictionary) resultValue;
foreach (dynamic kvp in expectedValueDictionary)
{
AssertAreEqual(kvp.Key, resultValueDictionary.Contains(kvp.Key));
AssertAreEqual(kvp.Value, resultValueDictionary[kvp.Key]);
}
}
else
{
Assert.AreEqual(expectedValue, resultValue);
}
}
}
foreach (var fieldInfo in expected.GetType().GetFields())
{
if (fieldInfo.CustomAttributes.Any(data => data.AttributeType == typeof(ProtoMemberAttribute)))
{
Assert.AreEqual(fieldInfo.GetValue(expected), fieldInfo.GetValue(result));
}
}
}
}
}

View File

@@ -25,6 +25,7 @@ using QuantConnect.Algorithm;
using QuantConnect.Algorithm.Framework.Alphas;
using QuantConnect.Data;
using QuantConnect.Data.Auxiliary;
using QuantConnect.Data.Custom.AlphaStreams;
using QuantConnect.Data.Market;
using QuantConnect.Data.UniverseSelection;
using QuantConnect.Indicators;
@@ -67,15 +68,16 @@ namespace QuantConnect.Tests.Common.Util
};
var orders = new List<Order> { new MarketOrder(btcusd, 1000, DateTime.UtcNow, "ExpensiveOrder") { Id = 1 } };
var packet1 = new AlphaResultPacket("1", 1, insights: insights);
var packet1 = new AlphaResultPacket("1", 1, insights: insights, portfolio: new AlphaStreamsPortfolioState { TotalPortfolioValue = 11 });
var packet2 = new AlphaResultPacket("1", 1, orders: orders);
var packet3 = new AlphaResultPacket("1", 1, orderEvents: orderEvents);
var packet3 = new AlphaResultPacket("1", 1, orderEvents: orderEvents, portfolio: new AlphaStreamsPortfolioState { TotalPortfolioValue = 12 });
var result = new List<AlphaResultPacket> { packet1, packet2, packet3 }.Batch();
Assert.AreEqual(2, result.Insights.Count);
Assert.AreEqual(2, result.OrderEvents.Count);
Assert.AreEqual(1, result.Orders.Count);
Assert.AreEqual(12, result.Portfolio.TotalPortfolioValue);
Assert.IsTrue(result.Insights.SequenceEqual(insights));
Assert.IsTrue(result.OrderEvents.SequenceEqual(orderEvents));