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 protected override string Name {
get; } =
"Collective2";
62 private static Lazy<RateGate> _tenSecondsRateLimiter =
new Lazy<RateGate>(() =>
new RateGate(100, TimeSpan.FromMilliseconds(1000)));
67 private static Lazy<RateGate> _hourlyRateLimiter =
new Lazy<RateGate>(() =>
new RateGate(1000, TimeSpan.FromHours(1)));
72 private static Lazy<RateGate> _dailyRateLimiter =
new Lazy<RateGate>(() =>
new RateGate(20000, TimeSpan.FromDays(1)));
85 _destination =
new Uri(
"https://api4-general.collective2.com/Strategies/SetDesiredPositions");
97 if (!base.Send(parameters))
107 _tenSecondsRateLimiter.Value.WaitToProceed();
108 _hourlyRateLimiter.Value.WaitToProceed();
109 _dailyRateLimiter.Value.WaitToProceed();
110 var result = SendPositions(message);
125 var targets = parameters.
Targets;
126 positions =
new List<Collective2Position>();
127 foreach (var target
in targets)
131 _algorithm.
Error(
"One portfolio target was null");
135 if (!ConvertTypeOfSymbol(target.Symbol, out
string typeOfSymbol))
140 var symbol = _algorithm.
Ticker(target.Symbol);
143 symbol = $
"@{SymbolRepresentation.GenerateFutureTicker(target.Symbol.ID.Symbol, target.Symbol.ID.Date, doubleDigitsYear: false, includeExpirationDate: false)}";
145 else if (target.Symbol.SecurityType.IsOption())
155 SymbolType = typeOfSymbol,
171 private bool ConvertTypeOfSymbol(
Symbol targetSymbol, out
string typeOfSymbol)
176 typeOfSymbol =
"stock";
179 typeOfSymbol =
"option";
182 typeOfSymbol =
"future";
185 typeOfSymbol =
"forex";
188 typeOfSymbol =
"NotImplemented";
192 if (typeOfSymbol ==
"NotImplemented")
194 _algorithm.
Error($
"{targetSymbol.SecurityType} security type is not supported by Collective2.");
210 if (numberShares ==
null)
212 throw new InvalidOperationException($
"Collective2 failed to calculate target quantity for {target}");
215 return (
int)numberShares.Quantity;
227 StrategyId = _systemId,
228 Positions = positions,
231 var jsonMessage = JsonConvert.SerializeObject(payload);
241 private bool SendPositions(
string message)
243 using var httpMessage =
new StringContent(message, Encoding.UTF8,
"application/json");
246 httpMessage.Headers.Add(
"X-AppId",
"OPA1N90E71");
249 HttpClient.DefaultRequestHeaders.Authorization =
new System.Net.Http.Headers.AuthenticationHeaderValue(
"Bearer", _apiKey);
252 using HttpResponseMessage response =
HttpClient.PostAsync(_destination, httpMessage).Result;
255 var responseObject = response.Content.ReadFromJsonAsync<C2Response>().
Result;
257 if (!response.IsSuccessStatusCode)
259 _algorithm.
Error($
"Collective2 API returned the following errors: {string.Join(",
", PrintErrors(responseObject.ResponseStatus.Errors))}");
262 else if (responseObject.Results.Count > 0)
264 _algorithm.
Debug($
"Collective2: NewSignals={string.Join(',', responseObject.Results[0].NewSignals)} | CanceledSignals={string.Join(',', responseObject.Results[0].CanceledSignals)}");
270 private static string PrintErrors(List<ResponseError> errors)
272 if (errors?.Count == 0)
277 StringBuilder sb =
new StringBuilder();
278 foreach (var error
in errors)
280 sb.AppendLine(CultureInfo.InvariantCulture, $
"({error.ErrorCode}) {error.FieldName}: {error.Message}");
283 return sb.ToString();
289 private class C2Response
291 [JsonProperty(PropertyName =
"Results")]
292 public virtual List<DesiredPositionResponse> Results {
get;
set; }
295 [JsonProperty(PropertyName =
"ResponseStatus")]
296 public ResponseStatus ResponseStatus {
get;
set; }
302 private class DesiredPositionResponse
304 [JsonProperty(PropertyName =
"NewSignals")]
305 public List<long> NewSignals {
get;
set; } =
new List<long>();
308 [JsonProperty(PropertyName =
"CanceledSignals")]
309 public List<long> CanceledSignals {
get;
set; } =
new List<long>();
315 private class ResponseStatus
334 [JsonProperty(PropertyName =
"ErrorCode")]
335 public string ErrorCode {
get;
set; }
338 [JsonProperty(PropertyName =
"Message")]
339 public string Message {
get;
set; }
342 [JsonProperty(PropertyName =
"Errors")]
343 public List<ResponseError> Errors {
get;
set; }
350 private class ResponseError
352 [JsonProperty(PropertyName =
"ErrorCode")]
353 public string ErrorCode {
get;
set; }
356 [JsonProperty(PropertyName =
"FieldName")]
357 public string FieldName {
get;
set; }
360 [JsonProperty(PropertyName =
"Message")]
361 public string Message {
get;
set; }
373 [JsonProperty(PropertyName =
"C2Symbol")]
380 [JsonProperty(PropertyName =
"Quantity")]
392 [JsonProperty(PropertyName =
"FullSymbol")]
399 [JsonProperty(PropertyName =
"SymbolType")]