16 using Newtonsoft.Json;
20 using System.Collections.Generic;
21 using System.Globalization;
22 using System.Net.Http;
23 using System.Net.Http.Json;
37 private readonly
string _apiKey;
42 private readonly
int _systemId;
52 private bool _isZeroPriceWarningPrinted;
62 protected override string Name {
get; } =
"Collective2";
67 private static Lazy<RateGate> _tenSecondsRateLimiter =
new Lazy<RateGate>(() =>
new RateGate(100, TimeSpan.FromMilliseconds(1000)));
72 private static Lazy<RateGate> _hourlyRateLimiter =
new Lazy<RateGate>(() =>
new RateGate(1000, TimeSpan.FromHours(1)));
77 private static Lazy<RateGate> _dailyRateLimiter =
new Lazy<RateGate>(() =>
new RateGate(20000, TimeSpan.FromDays(1)));
92 ?
"https://api4-wl.collective2.com/Strategies/SetDesiredPositions"
93 :
"https://api4-general.collective2.com/Strategies/SetDesiredPositions");
105 if (!base.Send(parameters))
115 _tenSecondsRateLimiter.Value.WaitToProceed();
116 _hourlyRateLimiter.Value.WaitToProceed();
117 _dailyRateLimiter.Value.WaitToProceed();
118 var result = SendPositions(message);
133 var targets = parameters.
Targets;
134 positions =
new List<Collective2Position>();
135 foreach (var target
in targets)
139 _algorithm.
Error(
"One portfolio target was null");
143 if (!ConvertTypeOfSymbol(target.Symbol, out
string typeOfSymbol))
148 var symbol = _algorithm.
Ticker(target.Symbol);
151 symbol = $
"@{SymbolRepresentation.GenerateFutureTicker(target.Symbol.ID.Symbol, target.Symbol.ID.Date, doubleDigitsYear: false, includeExpirationDate: false)}";
153 else if (target.Symbol.SecurityType.IsOption())
163 SymbolType = typeOfSymbol,
179 private bool ConvertTypeOfSymbol(
Symbol targetSymbol, out
string typeOfSymbol)
184 typeOfSymbol =
"stock";
187 typeOfSymbol =
"option";
190 typeOfSymbol =
"future";
193 typeOfSymbol =
"forex";
196 typeOfSymbol =
"option";
199 typeOfSymbol =
"NotImplemented";
203 if (typeOfSymbol ==
"NotImplemented")
205 _algorithm.
Error($
"{targetSymbol.SecurityType} security type is not supported by Collective2.");
221 if (numberShares ==
null)
223 if (algorithm.
Securities.TryGetValue(target.
Symbol, out var security) && security.Price == 0 && target.
Quantity == 0)
225 if (!_isZeroPriceWarningPrinted)
227 _isZeroPriceWarningPrinted =
true;
228 algorithm.
Debug($
"Warning: Collective2 failed to calculate target quantity for {target}. The price for {target.Symbol} is 0, and the target quantity is 0. Will return 0 for all similar cases.");
232 throw new InvalidOperationException($
"Collective2 failed to calculate target quantity for {target}");
235 return (
int)numberShares.Quantity;
247 StrategyId = _systemId,
248 Positions = positions,
251 var jsonMessage = JsonConvert.SerializeObject(payload);
261 private bool SendPositions(
string message)
263 using var httpMessage =
new StringContent(message, Encoding.UTF8,
"application/json");
266 httpMessage.Headers.Add(
"X-AppId",
"OPA1N90E71");
269 HttpClient.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue(
"Bearer", _apiKey);
275 var responseObject = response.Content.ReadFromJsonAsync<C2Response>().
Result;
277 if (!response.IsSuccessStatusCode)
279 _algorithm.
Error($
"Collective2 API returned the following errors: {string.Join(",
", PrintErrors(responseObject.ResponseStatus.Errors))}");
282 else if (responseObject.Results.Count > 0)
284 _algorithm.
Debug($
"Collective2: NewSignals={string.Join(',', responseObject.Results[0].NewSignals)} | CanceledSignals={string.Join(',', responseObject.Results[0].CanceledSignals)}");
290 private static string PrintErrors(List<ResponseError> errors)
292 if (errors?.Count == 0)
297 StringBuilder sb =
new StringBuilder();
298 foreach (var error
in errors)
300 sb.AppendLine(CultureInfo.InvariantCulture, $
"({error.ErrorCode}) {error.FieldName}: {error.Message}");
303 return sb.ToString();
309 private class C2Response
311 [JsonProperty(PropertyName =
"Results")]
312 public virtual List<DesiredPositionResponse> Results {
get;
set; }
315 [JsonProperty(PropertyName =
"ResponseStatus")]
316 public ResponseStatus ResponseStatus {
get;
set; }
322 private class DesiredPositionResponse
324 [JsonProperty(PropertyName =
"NewSignals")]
325 public List<long> NewSignals {
get;
set; } =
new List<long>();
328 [JsonProperty(PropertyName =
"CanceledSignals")]
329 public List<long> CanceledSignals {
get;
set; } =
new List<long>();
335 private class ResponseStatus
354 [JsonProperty(PropertyName =
"ErrorCode")]
355 public string ErrorCode {
get;
set; }
358 [JsonProperty(PropertyName =
"Message")]
359 public string Message {
get;
set; }
362 [JsonProperty(PropertyName =
"Errors")]
363 public List<ResponseError> Errors {
get;
set; }
370 private class ResponseError
372 [JsonProperty(PropertyName =
"ErrorCode")]
373 public string ErrorCode {
get;
set; }
376 [JsonProperty(PropertyName =
"FieldName")]
377 public string FieldName {
get;
set; }
380 [JsonProperty(PropertyName =
"Message")]
381 public string Message {
get;
set; }
393 [JsonProperty(PropertyName =
"C2Symbol")]
400 [JsonProperty(PropertyName =
"Quantity")]
412 [JsonProperty(PropertyName =
"FullSymbol")]
419 [JsonProperty(PropertyName =
"SymbolType")]