diff --git a/Common/Algorithm/Framework/Portfolio/SignalExports/VBaseSignalExport.cs b/Common/Algorithm/Framework/Portfolio/SignalExports/VBaseSignalExport.cs index b199d3de2a7c..1ab6275001a0 100644 --- a/Common/Algorithm/Framework/Portfolio/SignalExports/VBaseSignalExport.cs +++ b/Common/Algorithm/Framework/Portfolio/SignalExports/VBaseSignalExport.cs @@ -22,6 +22,7 @@ using System.Net.Http.Headers; using System.Security.Cryptography; using System.Text; +using System.Threading; namespace QuantConnect.Algorithm.Framework.Portfolio.SignalExports { @@ -112,33 +113,88 @@ public override bool Send(SignalExportTargetParameters parameters) return false; } - var csv = BuildCsv(parameters); - _requestsRateLimiter?.WaitToProceed(); - return Stamp(csv, parameters.Algorithm); + try + { + + var csv = BuildCsv(parameters); + _requestsRateLimiter?.WaitToProceed(); + return Stamp(csv, parameters.Algorithm); + } + catch (InvalidOperationException e) + { + parameters.Algorithm.Error($"vBase signal export failed: {e.Message}"); + return false; + } } /// - /// Builds a CSV (sym,wt) for the given targets converting percent holdings into absolute quantity using PortfolioTarget.Percent + /// Builds a CSV with header `sym,wt` that lists the normalized portfolio weights for every symbol in the + /// current portfolio unioned with the provided targets, converting quantities to value using current prices. /// /// Signal export parameters /// Resulting CSV string protected virtual string BuildCsv(SignalExportTargetParameters parameters) { - var algorithm = parameters.Algorithm; var csv = "sym,wt\n"; - var targets = parameters.Targets.Select(target => - PortfolioTarget.Percent(algorithm, target.Symbol, target.Quantity) - ) - .Where(absoluteTarget => absoluteTarget != null); + var weights = GetWeights(parameters); - foreach (var target in targets) + foreach (var weight in weights) { - csv += $"{target.Symbol.Value},{target.Quantity.ToStringInvariant()}\n"; + csv += $"{weight.Symbol},{weight.Weight.ToStringInvariant()}\n"; } return csv; } + private List<(Symbol Symbol, decimal Weight)> GetWeights(SignalExportTargetParameters parameters) + { + var algorithm = parameters.Algorithm; + List<(Symbol Symbol, decimal Value)> symbolValues = new(); + + // parameters targets contain only updates to the portfolio + // as we want to stamp weights for all positions, we need to union with current portfolio symbols + List allSymbols = algorithm.Portfolio.Keys.Union(parameters.Targets.Select(t => t.Symbol)).ToList(); + + foreach (Symbol symbol in allSymbols) + { + // if symbol is in parameters targets we take quantity from there + // otherwise we take current portfolio quantity + decimal quantity = parameters.Targets + .SingleOrDefault(t => t.Symbol == symbol) + ?.Quantity ?? algorithm.Portfolio[symbol].Quantity; + + if (algorithm.Securities.TryGetValue(symbol, out var security)) + { + // we use current price of the security to convert quantity into value, which will be used to calculate weights + symbolValues.Add((symbol, quantity * security.Price)); + } + else + { + // if we can't find the symbol in securities, we cannot calculate weights + throw new InvalidOperationException(Messages.PortfolioTarget.SymbolNotFound(symbol)); + } + } + + List<(Symbol Symbol, decimal Weight)> weights = new(); + + // get total value of the portfolio by summing values of all symbols + decimal sum = symbolValues.Sum(p => p.Value); + + if (sum == 0) + { + // if sum is 0 - no positions + // we cannot calculate weights, but we can still stamp an empty portfolio + return weights; + } + + foreach (var symbolValue in symbolValues) + { + weights.Add((symbolValue.Symbol, symbolValue.Value / sum)); + } + + return weights; + } + /// /// Sends the CSV payload to the vBase stamping API /// @@ -179,3 +235,5 @@ private bool Stamp(string csv, IAlgorithm algorithm) } } } + +