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)
}
}
}
+
+