17 using System.Collections.Generic;
21 using System.Net.Http;
22 using Newtonsoft.Json;
23 using Newtonsoft.Json.Linq;
25 using RestSharp.Extensions;
35 using System.Threading;
36 using System.Net.Http.Headers;
37 using System.Collections.Concurrent;
39 using Newtonsoft.Json.Serialization;
48 private readonly BlockingCollection<Lazy<HttpClient>> _clientPool;
49 private string _dataFolder;
56 ContractResolver =
new DefaultContractResolver
58 NamingStrategy =
new CamelCaseNamingStrategy
60 ProcessDictionaryKeys =
false,
61 OverrideSpecifiedNames =
true
76 _clientPool =
new BlockingCollection<Lazy<HttpClient>>(
new ConcurrentQueue<Lazy<HttpClient>>(), 5);
77 for (
int i = 0; i < _clientPool.BoundedCapacity; i++)
79 _clientPool.Add(
new Lazy<HttpClient>());
86 public virtual void Initialize(
int userId,
string token,
string dataFolder)
89 _dataFolder = dataFolder?.Replace(
"\\",
"/", StringComparison.InvariantCulture);
92 JsonConvert.DefaultSettings = () =>
new JsonSerializerSettings
114 var request =
new RestRequest(
"projects/create", Method.POST)
116 RequestFormat = DataFormat.Json
121 if (
string.IsNullOrEmpty(organizationId))
123 jsonParams = JsonConvert.SerializeObject(
new
131 jsonParams = JsonConvert.SerializeObject(
new
139 request.AddParameter(
"application/json", jsonParams, ParameterType.RequestBody);
153 var request =
new RestRequest(
"projects/read", Method.POST)
155 RequestFormat = DataFormat.Json
158 request.AddParameter(
"application/json", JsonConvert.SerializeObject(
new
161 }), ParameterType.RequestBody);
174 var request =
new RestRequest(
"projects/read", Method.POST)
176 RequestFormat = DataFormat.Json
194 var request =
new RestRequest(
"files/create", Method.POST)
196 RequestFormat = DataFormat.Json
199 request.AddParameter(
"application/json", JsonConvert.SerializeObject(
new
204 }), ParameterType.RequestBody);
221 var request =
new RestRequest(
"files/update", Method.POST)
223 RequestFormat = DataFormat.Json
226 request.AddParameter(
"application/json", JsonConvert.SerializeObject(
new
230 newName = newFileName
231 }), ParameterType.RequestBody);
248 var request =
new RestRequest(
"files/update", Method.POST)
250 RequestFormat = DataFormat.Json
253 request.AddParameter(
"application/json", JsonConvert.SerializeObject(
new
257 content = newFileContents
258 }), ParameterType.RequestBody);
273 var request =
new RestRequest(
"files/read", Method.POST)
275 RequestFormat = DataFormat.Json
278 request.AddParameter(
"application/json", JsonConvert.SerializeObject(
new
281 }), ParameterType.RequestBody);
294 var request =
new RestRequest(
"projects/nodes/read", Method.POST)
296 RequestFormat = DataFormat.Json
299 request.AddParameter(
"application/json", JsonConvert.SerializeObject(
new
302 }), ParameterType.RequestBody);
317 var request =
new RestRequest(
"projects/nodes/update", Method.POST)
319 RequestFormat = DataFormat.Json
322 request.AddParameter(
"application/json", JsonConvert.SerializeObject(
new
326 }), ParameterType.RequestBody);
341 var request =
new RestRequest(
"files/read", Method.POST)
343 RequestFormat = DataFormat.Json
346 request.AddParameter(
"application/json", JsonConvert.SerializeObject(
new
350 }), ParameterType.RequestBody);
361 var request =
new RestRequest(
"lean/versions/read", Method.POST)
363 RequestFormat = DataFormat.Json
379 var request =
new RestRequest(
"files/delete", Method.POST)
381 RequestFormat = DataFormat.Json
384 request.AddParameter(
"application/json", JsonConvert.SerializeObject(
new
388 }), ParameterType.RequestBody);
402 var request =
new RestRequest(
"projects/delete", Method.POST)
404 RequestFormat = DataFormat.Json
407 request.AddParameter(
"application/json", JsonConvert.SerializeObject(
new
410 }), ParameterType.RequestBody);
424 var request =
new RestRequest(
"compile/create", Method.POST)
426 RequestFormat = DataFormat.Json
429 request.AddParameter(
"application/json", JsonConvert.SerializeObject(
new
432 }), ParameterType.RequestBody);
447 var request =
new RestRequest(
"compile/read", Method.POST)
449 RequestFormat = DataFormat.Json
452 request.AddParameter(
"application/json", JsonConvert.SerializeObject(
new
456 }), ParameterType.RequestBody);
470 throw new NotImplementedException($
"{nameof(Api)} does not support sending notifications");
483 var request =
new RestRequest(
"backtests/create", Method.POST)
485 RequestFormat = DataFormat.Json
488 request.AddParameter(
"application/json", JsonConvert.SerializeObject(
new
493 }), ParameterType.RequestBody);
498 result.Backtest.Success = result.Success;
499 result.Backtest.Errors = result.Errors;
502 return result.Backtest;
515 var request =
new RestRequest(
"backtests/read", Method.POST)
517 RequestFormat = DataFormat.Json
520 request.AddParameter(
"application/json", JsonConvert.SerializeObject(
new
524 }), ParameterType.RequestBody);
531 result.Backtest =
new Backtest { BacktestId = backtestId };
534 else if (getCharts && result.Backtest.Completed)
537 var updatedCharts =
new Dictionary<string, Chart>();
540 foreach (var chart
in result.Backtest.Charts)
542 if (!chart.Value.Series.IsNullOrEmpty())
547 var chartRequest =
new RestRequest(
"backtests/read", Method.POST)
549 RequestFormat = DataFormat.Json
552 chartRequest.AddParameter(
"application/json", JsonConvert.SerializeObject(
new
557 }), ParameterType.RequestBody);
562 updatedCharts.Add(chart.Key, chartResponse.Backtest.Charts[chart.Key]);
567 foreach(var updatedChart
in updatedCharts)
569 result.Backtest.Charts[updatedChart.Key] = updatedChart.Value;
574 result.Backtest.Success = result.Success;
575 result.Backtest.Errors = result.Errors;
578 return result.Backtest;
591 public List<ApiOrderResponse>
ReadBacktestOrders(
int projectId,
string backtestId,
int start = 0,
int end = 100)
593 var request =
new RestRequest(
"backtests/orders/read", Method.POST)
595 RequestFormat = DataFormat.Json
598 request.AddParameter(
"application/json", JsonConvert.SerializeObject(
new
604 }), ParameterType.RequestBody);
606 return MakeRequestOrThrow<OrdersResponseWrapper>(request, nameof(
ReadBacktestOrders)).Orders;
621 var request =
new RestRequest(
"backtests/chart/read", Method.POST)
623 RequestFormat = DataFormat.Json
626 request.AddParameter(
"application/json", JsonConvert.SerializeObject(
new
634 }), ParameterType.RequestBody);
639 var finish = DateTime.UtcNow.AddMinutes(1);
640 while (DateTime.UtcNow < finish && result.Chart ==
null)
660 var request =
new RestRequest(
"backtests/update", Method.POST)
662 RequestFormat = DataFormat.Json
665 request.AddParameter(
"application/json", JsonConvert.SerializeObject(
new
671 }), ParameterType.RequestBody);
686 var request =
new RestRequest(
"backtests/list", Method.POST)
688 RequestFormat = DataFormat.Json
691 var obj =
new Dictionary<string, object>()
693 {
"projectId", projectId },
694 {
"includeStatistics", includeStatistics }
697 request.AddParameter(
"application/json", JsonConvert.SerializeObject(obj), ParameterType.RequestBody);
712 var request =
new RestRequest(
"backtests/delete", Method.POST)
714 RequestFormat = DataFormat.Json
717 request.AddParameter(
"application/json", JsonConvert.SerializeObject(
new
721 }), ParameterType.RequestBody);
736 var request =
new RestRequest(
"backtests/tags/update", Method.POST)
738 RequestFormat = DataFormat.Json
741 request.AddParameter(
"application/json", JsonConvert.SerializeObject(
new
746 }), ParameterType.RequestBody);
763 var request =
new RestRequest(
"backtests/insights/read", Method.POST)
765 RequestFormat = DataFormat.Json,
768 var diff = end - start;
771 throw new ArgumentException($
"The difference between the start and end index of the insights must be smaller than 100, but it was {diff}.");
780 {
"projectId", projectId },
781 {
"backtestId", backtestId },
786 request.AddParameter(
"application/json", JsonConvert.SerializeObject(obj), ParameterType.RequestBody);
816 Dictionary<string, object> brokerageSettings,
817 string versionId =
"-1",
818 Dictionary<string, object> dataProviders =
null)
820 var request =
new RestRequest(
"live/create", Method.POST)
822 RequestFormat = DataFormat.Json
825 request.AddParameter(
"application/json", JsonConvert.SerializeObject(
834 ), ParameterType.RequestBody);
864 return CreateLiveAlgorithm(projectId, compileId, nodeId, ConvertToDictionary(brokerageSettings), versionId, dataProviders !=
null ? ConvertToDictionary(dataProviders) :
null);
871 private static Dictionary<string, object> ConvertToDictionary(PyObject brokerageSettings)
875 var stringBrokerageSettings = brokerageSettings.ToString();
876 return JsonConvert.DeserializeObject<Dictionary<string, object>>(stringBrokerageSettings);
889 DateTime? startTime =
null,
890 DateTime? endTime =
null)
893 if (status.HasValue &&
899 throw new ArgumentException(
900 "The Api only supports Algorithm Statuses of Running, Stopped, RuntimeError and Liquidated");
903 var request =
new RestRequest(
"live/list", Method.POST)
905 RequestFormat = DataFormat.Json
911 JObject obj =
new JObject
913 {
"start", epochStartTime },
914 {
"end", epochEndTime }
919 obj.Add(
"status", status.ToString());
922 request.AddParameter(
"application/json", JsonConvert.SerializeObject(obj), ParameterType.RequestBody);
937 var request =
new RestRequest(
"live/read", Method.POST)
939 RequestFormat = DataFormat.Json
942 request.AddParameter(
"application/json", JsonConvert.SerializeObject(
new
946 }), ParameterType.RequestBody);
959 var request =
new RestRequest(
"live/portfolio/read", Method.POST)
961 RequestFormat = DataFormat.Json
964 request.AddParameter(
"application/json", JsonConvert.SerializeObject(
new
967 }), ParameterType.RequestBody);
982 public List<ApiOrderResponse>
ReadLiveOrders(
int projectId,
int start = 0,
int end = 100)
984 var request =
new RestRequest(
"live/orders/read", Method.POST)
986 RequestFormat = DataFormat.Json
989 request.AddParameter(
"application/json", JsonConvert.SerializeObject(
new
994 }), ParameterType.RequestBody);
996 return MakeRequestOrThrow<OrdersResponseWrapper>(request, nameof(
ReadLiveOrders)).Orders;
1007 var request =
new RestRequest(
"live/update/liquidate", Method.POST)
1009 RequestFormat = DataFormat.Json
1012 request.AddParameter(
"application/json", JsonConvert.SerializeObject(
new
1015 }), ParameterType.RequestBody);
1028 var request =
new RestRequest(
"live/update/stop", Method.POST)
1030 RequestFormat = DataFormat.Json
1033 request.AddParameter(
"application/json", JsonConvert.SerializeObject(
new
1036 }), ParameterType.RequestBody);
1050 var request =
new RestRequest(
"live/commands/create", Method.POST)
1052 RequestFormat = DataFormat.Json
1055 request.AddParameter(
"application/json", JsonConvert.SerializeObject(
new
1059 }), ParameterType.RequestBody);
1075 var logLinesNumber = endLine - startLine;
1076 if (logLinesNumber > 250)
1078 throw new ArgumentException($
"The maximum number of log lines allowed is 250. But the number of log lines was {logLinesNumber}.");
1081 var request =
new RestRequest(
"live/logs/read", Method.POST)
1083 RequestFormat = DataFormat.Json
1086 request.AddParameter(
"application/json", JsonConvert.SerializeObject(
new
1093 }), ParameterType.RequestBody);
1110 var request =
new RestRequest(
"live/chart/read", Method.POST)
1112 RequestFormat = DataFormat.Json
1115 request.AddParameter(
"application/json", JsonConvert.SerializeObject(
new
1122 }), ParameterType.RequestBody);
1127 var finish = DateTime.UtcNow.AddMinutes(1);
1128 while(DateTime.UtcNow < finish && result.Chart ==
null)
1146 var request =
new RestRequest(
"live/insights/read", Method.POST)
1148 RequestFormat = DataFormat.Json,
1151 var diff = end - start;
1154 throw new ArgumentException($
"The difference between the start and end index of the insights must be smaller than 100, but it was {diff}.");
1161 JObject obj =
new JObject
1163 {
"projectId", projectId },
1168 request.AddParameter(
"application/json", JsonConvert.SerializeObject(obj), ParameterType.RequestBody);
1182 if (filePath ==
null)
1184 throw new ArgumentException(
"Api.ReadDataLink(): Filepath must not be null");
1190 var request =
new RestRequest(
"data/read", Method.POST)
1192 RequestFormat = DataFormat.Json
1195 request.AddParameter(
"application/json", JsonConvert.SerializeObject(
new
1200 }), ParameterType.RequestBody);
1212 if (filePath ==
null)
1214 throw new ArgumentException(
"Api.ReadDataDirectory(): Filepath must not be null");
1222 if (filePath.Count(x => x ==
'/') < 3)
1224 throw new ArgumentException($
"Api.ReadDataDirectory(): Data directory requested must be at least" +
1225 $
" three directories deep. FilePath: {filePath}");
1228 var request =
new RestRequest(
"data/list", Method.POST)
1230 RequestFormat = DataFormat.Json
1233 request.AddParameter(
"application/json", JsonConvert.SerializeObject(
new
1236 }), ParameterType.RequestBody);
1247 var request =
new RestRequest(
"data/prices", Method.POST)
1249 RequestFormat = DataFormat.Json
1252 request.AddParameter(
"application/json", JsonConvert.SerializeObject(
new
1255 }), ParameterType.RequestBody);
1269 var request =
new RestRequest(
"backtests/read/report", Method.POST)
1271 RequestFormat = DataFormat.Json
1274 request.AddParameter(
"application/json", JsonConvert.SerializeObject(
new
1278 }), ParameterType.RequestBody);
1281 var finish = DateTime.UtcNow.AddMinutes(1);
1282 while (DateTime.UtcNow < finish && !report.
Success)
1284 Thread.Sleep(10000);
1303 if (!dataLink.Success)
1305 Log.
Trace($
"Api.DownloadData(): Failed to get link for {filePath}. " +
1306 $
"Errors: {string.Join(',', dataLink.Errors)}");
1311 var directory = Path.GetDirectoryName(filePath);
1312 if (!Directory.Exists(directory))
1314 Directory.CreateDirectory(directory);
1317 var client = BorrowClient();
1321 var uri =
new Uri(dataLink.Link);
1322 using var dataStream = client.Value.GetStreamAsync(uri);
1325 dataStream.Result.CopyTo(fileStream);
1329 Log.
Error($
"Api.DownloadData(): Failed to download zip for path ({filePath})");
1334 ReturnClient(client);
1350 ChartSubscription =
"*"
1381 public virtual void SendStatistics(
string algorithmId, decimal unrealized, decimal fees, decimal netProfit, decimal holdings, decimal equity, decimal netReturn, decimal volume,
int trades,
double sharpe)
1393 public virtual void SendUserEmail(
string algorithmId,
string subject,
string body)
1406 public virtual string Download(
string address, IEnumerable<KeyValuePair<string, string>> headers,
string userName,
string password)
1408 return Encoding.UTF8.GetString(
DownloadBytes(address, headers, userName, password));
1420 public virtual byte[]
DownloadBytes(
string address, IEnumerable<KeyValuePair<string, string>> headers,
string userName,
string password)
1422 var client = BorrowClient();
1425 client.Value.DefaultRequestHeaders.Clear();
1428 client.Value.DefaultRequestHeaders.TryAddWithoutValidation(
"user-agent",
"QCAlgorithm.Download(): User Agent Header");
1430 if (headers !=
null)
1432 foreach (var header
in headers)
1434 client.Value.DefaultRequestHeaders.TryAddWithoutValidation(header.Key, header.Value);
1438 if (!userName.IsNullOrEmpty() || !password.IsNullOrEmpty())
1440 var credentials = Convert.ToBase64String(Encoding.ASCII.GetBytes($
"{userName}:{password}"));
1441 client.Value.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue(
"Basic", credentials);
1444 return client.Value.GetByteArrayAsync(
new Uri(address)).Result;
1446 catch (Exception exception)
1448 var message = $
"Api.DownloadBytes(): Failed to download data from {address}";
1449 if (!userName.IsNullOrEmpty() || !password.IsNullOrEmpty())
1451 message += $
" with username: {userName} and password {password}";
1454 throw new WebException($
"{message}. Please verify the source for missing http:// or https://", exception);
1458 client.Value.DefaultRequestHeaders.Clear();
1459 ReturnClient(client);
1470 _clientPool.CompleteAdding();
1471 foreach (var client
in _clientPool.GetConsumingEnumerable())
1473 if (client.IsValueCreated)
1475 client.Value.DisposeSafely();
1478 _clientPool.DisposeSafely();
1489 var data = $
"{token}:{timestamp.ToStringInvariant()}";
1490 return data.ToSHA256();
1499 var request =
new RestRequest(
"account/read", Method.POST)
1501 RequestFormat = DataFormat.Json
1504 if (organizationId !=
null)
1506 request.AddParameter(
"application/json", JsonConvert.SerializeObject(
new { organizationId }), ParameterType.RequestBody);
1520 var request =
new RestRequest(
"organizations/read", Method.POST)
1522 RequestFormat = DataFormat.Json
1525 if (organizationId !=
null)
1527 request.AddParameter(
"application/json", JsonConvert.SerializeObject(
new { organizationId }), ParameterType.RequestBody);
1531 return response.Organization;
1552 decimal? targetValue,
1555 HashSet<OptimizationParameter> parameters,
1556 IReadOnlyList<Constraint> constraints)
1558 var request =
new RestRequest(
"optimizations/estimate", Method.POST)
1560 RequestFormat = DataFormat.Json
1563 request.AddParameter(
"application/json", JsonConvert.SerializeObject(
new
1577 return response.Estimate;
1601 decimal? targetValue,
1604 HashSet<OptimizationParameter> parameters,
1605 IReadOnlyList<Constraint> constraints,
1606 decimal estimatedCost,
1610 var request =
new RestRequest(
"optimizations/create", Method.POST)
1612 RequestFormat = DataFormat.Json
1615 request.AddParameter(
"application/json", JsonConvert.SerializeObject(
new
1632 return result.Optimizations.FirstOrDefault();
1642 var request =
new RestRequest(
"optimizations/list", Method.POST)
1644 RequestFormat = DataFormat.Json
1647 request.AddParameter(
"application/json", JsonConvert.SerializeObject(
new
1650 }), ParameterType.RequestBody);
1653 return result.Optimizations;
1663 var request =
new RestRequest(
"optimizations/read", Method.POST)
1665 RequestFormat = DataFormat.Json
1668 request.AddParameter(
"application/json", JsonConvert.SerializeObject(
new
1671 }), ParameterType.RequestBody);
1674 return response.Optimization;
1684 var request =
new RestRequest(
"optimizations/abort", Method.POST)
1686 RequestFormat = DataFormat.Json
1689 request.AddParameter(
"application/json", JsonConvert.SerializeObject(
new
1692 }), ParameterType.RequestBody);
1706 var request =
new RestRequest(
"optimizations/update", Method.POST)
1708 RequestFormat = DataFormat.Json
1711 var obj =
new JObject
1713 {
"optimizationId", optimizationId }
1716 if (name.HasValue())
1718 obj.Add(
"name", name);
1721 request.AddParameter(
"application/json", JsonConvert.SerializeObject(obj), ParameterType.RequestBody);
1734 var request =
new RestRequest(
"optimizations/delete", Method.POST)
1736 RequestFormat = DataFormat.Json
1739 request.AddParameter(
"application/json", JsonConvert.SerializeObject(
new
1742 }), ParameterType.RequestBody);
1755 public bool GetObjectStore(
string organizationId, List<string> keys,
string destinationFolder =
null)
1757 var request =
new RestRequest(
"object/get", Method.POST)
1759 RequestFormat = DataFormat.Json
1762 request.AddParameter(
"application/json", JsonConvert.SerializeObject(
new
1766 }), ParameterType.RequestBody);
1770 if (result ==
null || !result.Success)
1772 Log.
Error($
"Api.GetObjectStore(): Failed to get the jobId to request the download URL for the object store files."
1773 + (result !=
null ? $
" Errors: {string.Join(",
", result.Errors)}" :
""));
1777 var jobId = result.JobId;
1778 var getUrlRequest =
new RestRequest(
"object/get", Method.POST)
1780 RequestFormat = DataFormat.Json
1782 getUrlRequest.AddParameter(
"application/json", JsonConvert.SerializeObject(
new
1786 }), ParameterType.RequestBody);
1788 var frontier = DateTime.UtcNow + TimeSpan.FromMinutes(5);
1789 while (
string.IsNullOrEmpty(result?.Url) && (DateTime.UtcNow < frontier))
1795 if (result ==
null ||
string.IsNullOrEmpty(result.Url))
1797 Log.
Error($
"Api.GetObjectStore(): Failed to get the download URL from the jobId {jobId}."
1798 + (result !=
null ? $
" Errors: {string.Join(",
", result.Errors)}" :
""));
1802 var directory = destinationFolder ?? Directory.GetCurrentDirectory();
1803 var client = BorrowClient();
1807 if (client.Value.Timeout != TimeSpan.FromMinutes(20))
1809 client.Value.Timeout = TimeSpan.FromMinutes(20);
1813 var uri =
new Uri(result.Url);
1814 using var byteArray = client.Value.GetByteArrayAsync(uri);
1820 Log.
Error($
"Api.GetObjectStore(): Failed to download zip for path ({directory}). Error: {e.Message}");
1825 ReturnClient(client);
1840 var request =
new RestRequest(
"object/properties", Method.POST)
1842 RequestFormat = DataFormat.Json
1845 request.AddParameter(
"organizationId", organizationId);
1846 request.AddParameter(
"key", key);
1850 if (result ==
null || !result.Success)
1852 Log.
Error($
"Api.ObjectStore(): Failed to get the properties for the object store key {key}." + (result !=
null ? $
" Errors: {string.Join(",
", result.Errors)}" :
""));
1866 var request =
new RestRequest(
"object/set", Method.POST)
1868 RequestFormat = DataFormat.Json
1871 request.AddParameter(
"organizationId", organizationId);
1872 request.AddParameter(
"key", key);
1873 request.AddFileBytes(
"objectData", objectData,
"objectData");
1874 request.AlwaysMultipartFormData =
true;
1888 var request =
new RestRequest(
"object/delete", Method.POST)
1890 RequestFormat = DataFormat.Json
1893 var obj =
new Dictionary<string, object>
1895 {
"organizationId", organizationId },
1899 request.AddParameter(
"application/json", JsonConvert.SerializeObject(obj), ParameterType.RequestBody);
1913 var request =
new RestRequest(
"object/list", Method.POST)
1915 RequestFormat = DataFormat.Json
1918 var obj =
new Dictionary<string, object>
1920 {
"organizationId", organizationId },
1924 request.AddParameter(
"application/json", JsonConvert.SerializeObject(obj), ParameterType.RequestBody);
1938 if (filePath ==
null)
1940 Log.
Error(
"Api.FormatPathForDataRequest(): Cannot format null string");
1946 dataFolder = dataFolder.Replace(
"\\",
"/", StringComparison.InvariantCulture);
1947 filePath = filePath.Replace(
"\\",
"/", StringComparison.InvariantCulture);
1950 if (filePath.StartsWith(dataFolder, StringComparison.InvariantCulture))
1952 filePath = filePath.Substring(dataFolder.Length);
1956 filePath = filePath.TrimStart(
'/');
1963 private T MakeRequestOrThrow<T>(RestRequest request,
string callerName)
1968 var errors =
string.Empty;
1969 if (result !=
null && result.Errors !=
null && result.Errors.Count > 0)
1971 errors = $
". Errors: ['{string.Join(",
", result.Errors)}']";
1973 throw new WebException($
"{callerName} api request failed{errors}");
1982 private Lazy<HttpClient> BorrowClient()
1984 using var cancellationTokenSource =
new CancellationTokenSource(TimeSpan.FromMinutes(10));
1985 return _clientPool.Take(cancellationTokenSource.Token);
1991 private void ReturnClient(Lazy<HttpClient> client)
1993 _clientPool.Add(client);