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;
47 private readonly Uri _destination;
57 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)));
90 _destination =
new Uri(
"https://api4-general.collective2.com/Strategies/SetDesiredPositions");
102 if (!base.Send(parameters))
112 _tenSecondsRateLimiter.Value.WaitToProceed();
113 _hourlyRateLimiter.Value.WaitToProceed();
114 _dailyRateLimiter.Value.WaitToProceed();
115 var result = SendPositions(message);
130 var targets = parameters.
Targets;
131 positions =
new List<Collective2Position>();
132 foreach (var target
in targets)
136 _algorithm.
Error(
"One portfolio target was null");
140 if (!ConvertTypeOfSymbol(target.Symbol, out
string typeOfSymbol))
145 var symbol = _algorithm.
Ticker(target.Symbol);
148 symbol = $
"@{SymbolRepresentation.GenerateFutureTicker(target.Symbol.ID.Symbol, target.Symbol.ID.Date, doubleDigitsYear: false, includeExpirationDate: false)}";
150 else if (target.Symbol.SecurityType.IsOption())
160 SymbolType = typeOfSymbol,
176 private bool ConvertTypeOfSymbol(
Symbol targetSymbol, out
string typeOfSymbol)
181 typeOfSymbol =
"stock";
184 typeOfSymbol =
"option";
187 typeOfSymbol =
"future";
190 typeOfSymbol =
"forex";
193 typeOfSymbol =
"option";
196 typeOfSymbol =
"NotImplemented";
200 if (typeOfSymbol ==
"NotImplemented")
202 _algorithm.
Error($
"{targetSymbol.SecurityType} security type is not supported by Collective2.");
218 if (numberShares ==
null)
220 if (algorithm.
Securities.TryGetValue(target.
Symbol, out var security) && security.Price == 0 && target.
Quantity == 0)
222 if (!_isZeroPriceWarningPrinted)
224 _isZeroPriceWarningPrinted =
true;
225 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.");
229 throw new InvalidOperationException($
"Collective2 failed to calculate target quantity for {target}");
232 return (
int)numberShares.Quantity;
244 StrategyId = _systemId,
245 Positions = positions,
248 var jsonMessage = JsonConvert.SerializeObject(payload);
258 private bool SendPositions(
string message)
260 using var httpMessage =
new StringContent(message, Encoding.UTF8,
"application/json");
263 httpMessage.Headers.Add(
"X-AppId",
"OPA1N90E71");
266 HttpClient.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue(
"Bearer", _apiKey);
269 using HttpResponseMessage response =
HttpClient.PostAsync(_destination, httpMessage).Result;
272 var responseObject = response.Content.ReadFromJsonAsync<C2Response>().
Result;
274 if (!response.IsSuccessStatusCode)
276 _algorithm.
Error($
"Collective2 API returned the following errors: {string.Join(",
", PrintErrors(responseObject.ResponseStatus.Errors))}");
279 else if (responseObject.Results.Count > 0)
281 _algorithm.
Debug($
"Collective2: NewSignals={string.Join(',', responseObject.Results[0].NewSignals)} | CanceledSignals={string.Join(',', responseObject.Results[0].CanceledSignals)}");
287 private static string PrintErrors(List<ResponseError> errors)
289 if (errors?.Count == 0)
294 StringBuilder sb =
new StringBuilder();
295 foreach (var error
in errors)
297 sb.AppendLine(CultureInfo.InvariantCulture, $
"({error.ErrorCode}) {error.FieldName}: {error.Message}");
300 return sb.ToString();
306 private class C2Response
308 [JsonProperty(PropertyName =
"Results")]
309 public virtual List<DesiredPositionResponse> Results {
get;
set; }
312 [JsonProperty(PropertyName =
"ResponseStatus")]
313 public ResponseStatus ResponseStatus {
get;
set; }
319 private class DesiredPositionResponse
321 [JsonProperty(PropertyName =
"NewSignals")]
322 public List<long> NewSignals {
get;
set; } =
new List<long>();
325 [JsonProperty(PropertyName =
"CanceledSignals")]
326 public List<long> CanceledSignals {
get;
set; } =
new List<long>();
332 private class ResponseStatus
351 [JsonProperty(PropertyName =
"ErrorCode")]
352 public string ErrorCode {
get;
set; }
355 [JsonProperty(PropertyName =
"Message")]
356 public string Message {
get;
set; }
359 [JsonProperty(PropertyName =
"Errors")]
360 public List<ResponseError> Errors {
get;
set; }
367 private class ResponseError
369 [JsonProperty(PropertyName =
"ErrorCode")]
370 public string ErrorCode {
get;
set; }
373 [JsonProperty(PropertyName =
"FieldName")]
374 public string FieldName {
get;
set; }
377 [JsonProperty(PropertyName =
"Message")]
378 public string Message {
get;
set; }
390 [JsonProperty(PropertyName =
"C2Symbol")]
397 [JsonProperty(PropertyName =
"Quantity")]
409 [JsonProperty(PropertyName =
"FullSymbol")]
416 [JsonProperty(PropertyName =
"SymbolType")]