Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Bitget.Net/Bitget.Net.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net8.0;net9.0;net10.0;netstandard2.0;netstandard2.1</TargetFrameworks>
<LangVersion>latest</LangVersion>
Expand Down Expand Up @@ -56,7 +56,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="CryptoExchange.Net" Version="12.1.0" />
<PackageReference Include="CryptoExchange.Net" Version="12.2.0" />
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="10.0.101">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
Expand Down
4 changes: 4 additions & 0 deletions Bitget.Net/Clients/FuturesApiV2/BitgetRestClientFuturesApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,15 @@ internal partial class BitgetRestClientFuturesApi : RestApiClient<BitgetEnvironm
/// <inheritdoc />
public string ExchangeName => "Bitget";

internal BitgetRestClient _baseClient;

protected override IRestMessageHandler MessageHandler { get; } = new BitgetRestMessageHandler(BitgetErrors.RestErrors);

internal BitgetRestClientFuturesApi(ILoggerFactory? loggerFactory, HttpClient? httpClient, BitgetRestClient baseClient, BitgetRestOptions options)
: base(loggerFactory, BitgetExchange.Metadata.Id, httpClient, options.Environment.RestBaseAddress, options, options.FuturesOptions)
{
_baseClient = baseClient;

Account = new BitgetRestClientFuturesApiAccount(this);
ExchangeData = new BitgetRestClientFuturesApiExchangeData(this);
Trading = new BitgetRestClientFuturesApiTrading(this);
Expand Down
100 changes: 68 additions & 32 deletions Bitget.Net/Clients/FuturesApiV2/BitgetRestClientFuturesApiShared.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
using Bitget.Net.Enums;
using Bitget.Net.Enums.Uta;
using Bitget.Net.Enums.V2;
using Bitget.Net.Interfaces.Clients.FuturesApiV2;
using Bitget.Net.Objects.Models;
using Bitget.Net.Objects.Models.V2;
using CryptoExchange.Net;
using CryptoExchange.Net.Interfaces;
using CryptoExchange.Net.Objects;
using CryptoExchange.Net.Objects.Errors;
using CryptoExchange.Net.SharedApis;
Expand Down Expand Up @@ -171,6 +174,7 @@ async Task<HttpResult<SharedBookTicker>> IBookTickerRestClient.GetBookTickerAsyn

#region Futures Symbol client

SharedSymbolCatalog? IFuturesSymbolRestClient.FuturesSymbolCatalog => ExchangeSymbolCache.GetSymbolCatalog(_exchangeName, _topicId, EnvironmentName, null);
GetFuturesSymbolsOptions IFuturesSymbolRestClient.GetFuturesSymbolsOptions { get; } = new GetFuturesSymbolsOptions(_exchangeName, false)
{
RequiredExchangeParameters = new List<ParameterDescription>
Expand All @@ -184,46 +188,71 @@ async Task<HttpResult<SharedFuturesSymbol[]>> IFuturesSymbolRestClient.GetFuture
if (validationError != null)
return HttpResult.Fail<SharedFuturesSymbol[]>(Exchange, validationError);

var productType = GetProductType(request.TradingMode, request.ExchangeParameters);
var result = await ExchangeData.GetContractsAsync(
productType,
var productCategory = GetProductCategory(request.TradingMode, request.ExchangeParameters);
var result = await _baseClient.UnifiedApi.ExchangeData.GetFuturesSymbolsAsync(
productCategory,
ct: ct).ConfigureAwait(false);
if (!result.Success)
return HttpResult.Fail<SharedFuturesSymbol[]>(result);

IEnumerable<BitgetContract> data = result.Data;
if (request.TradingMode != null)
{
data = data
.Where(x => ((request.TradingMode == TradingMode.PerpetualInverse || request.TradingMode == TradingMode.PerpetualLinear) && x.ContractType == ContractType.Perpetual)
|| ((request.TradingMode == TradingMode.DeliveryLinear || request.TradingMode == TradingMode.DeliveryInverse) && x.ContractType == ContractType.Delivery));
}
var data = result.Data
.Select(x => ParseSymbol(x, productCategory))
.ToArray();

var response = HttpResult.Ok(result, data.Select(s =>
new SharedFuturesSymbol(
productType == BitgetProductTypeV2.CoinFutures && s.ContractType == ContractType.Delivery ? TradingMode.DeliveryInverse :
productType == BitgetProductTypeV2.CoinFutures && s.ContractType == ContractType.Perpetual ? TradingMode.PerpetualInverse :
s.DeliveryPeriod.HasValue ? TradingMode.DeliveryLinear :
TradingMode.PerpetualLinear,
s.BaseAsset,
s.QuoteAsset,
s.Symbol,
s.Status == Enums.V2.FuturesSymbolStatus.Normal)
ExchangeSymbolCache.UpdateSymbolInfo(_topicId, EnvironmentName, productCategory.ToString(), data);
return HttpResult.Ok(result, SharedUtils.ApplySymbolFilter(data, request));
}

private SharedFuturesSymbol ParseSymbol(BitgetUaFuturesSymbol s, ProductCategory productCategory)
{
var result = new SharedFuturesSymbol(
productCategory == ProductCategory.CoinFutures && s.Type == ContractType.Delivery ? TradingMode.DeliveryInverse :
productCategory == ProductCategory.CoinFutures && s.Type == ContractType.Perpetual ? TradingMode.PerpetualInverse :
s.DeliveryPeriod.HasValue ? TradingMode.DeliveryLinear :
TradingMode.PerpetualLinear,
s.BaseAsset,
s.QuoteAsset,
s.Symbol,
s.Status == InstrumentStatus.Online)
{
MinTradeQuantity = s.MinOrderQuantity,
PriceDecimals = s.PriceDecimals,
QuantityDecimals = s.QuantityDecimals,
PriceDecimals = s.PricePrecision,
QuantityDecimals = s.QuantityPrecision,
DeliveryTime = s.DeliveryTime,
PriceStep = s.PriceStep,
QuantityStep = s.QuantityStep,
PriceStep = s.PriceMultiplier,
QuantityStep = s.QuantityMultiplier,
ContractSize = 1,
MaxShortLeverage = s.MaxLeverage,
MaxLongLeverage = s.MaxLeverage,
MaxTradeQuantity = s.MaxLimitOrderQuantity == null && s.MaxLimitOrderQuantity == null ? null : Math.Min(s.MaxLimitOrderQuantity ?? decimal.MaxValue, s.MaxMarketOrderQuantity ?? decimal.MaxValue)
}).ToArray());
MaxTradeQuantity = Math.Min(s.MaxOrderQuantity, s.MaxMarketOrderQuantity),
DisplayName = s.Symbol,
};

if (productCategory != ProductCategory.CoinFutures)
{
result.BaseAssetType = s.IsRwa ? SharedAssetType.TradFi : SharedAssetType.Crypto;
result.BaseAssetSubType = ParseSymbolSubType(s);
result.QuoteAssetType = SharedAssetType.Crypto;
result.QuoteAssetSubType = SharedAssetSubType.StableCoin;
}
else
{
result.BaseAssetType = SharedAssetType.Crypto;
result.QuoteAssetType = SharedAssetType.Fiat;
}

ExchangeSymbolCache.UpdateSymbolInfo(_topicId, EnvironmentName, null, response.Data!);
return response;
return result;
}

private SharedAssetSubType? ParseSymbolSubType(BitgetUaFuturesSymbol s)
{
if (s.SymbolType == SymbolType.Commodity || s.SymbolType == SymbolType.Metal)
return SharedAssetSubType.Commodity;

if (s.SymbolType == SymbolType.Stock)
return SharedAssetSubType.Equity;

return null;
}

async Task<ExchangeCallResult<SharedSymbol[]>> IFuturesSymbolRestClient.GetFuturesSymbolsForBaseAssetAsync(string baseAsset)
Expand Down Expand Up @@ -1490,11 +1519,18 @@ private BitgetProductTypeV2 GetProductType(TradingMode? tradingMode, ExchangePar
return (BitgetProductTypeV2)Enum.Parse(typeof(BitgetProductTypeV2), productTypeStr!);
}

public static DateTime RoundUp(DateTime dt, TimeSpan d)
private ProductCategory GetProductCategory(TradingMode? tradingMode, ExchangeParameters? exchangeParameters)
{
var modTicks = dt.Ticks % d.Ticks;
var delta = modTicks != 0 ? d.Ticks - modTicks : 0;
return new DateTime(dt.Ticks + delta, dt.Kind);
var productType = GetProductType(tradingMode, exchangeParameters);
if (productType == BitgetProductTypeV2.CoinFutures)
return ProductCategory.CoinFutures;
else if(productType == BitgetProductTypeV2.UsdtFutures)
return ProductCategory.UsdtFutures;
else if (productType == BitgetProductTypeV2.UsdcFutures)
return ProductCategory.UsdcFutures;

throw new ArgumentException("ProductType", $"Invalid product type {productType} for futures");
}

}
}
4 changes: 4 additions & 0 deletions Bitget.Net/Clients/SpotApiV2/BitgetRestClientSpotApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,13 @@ internal partial class BitgetRestClientSpotApi : RestApiClient<BitgetEnvironment

protected override IRestMessageHandler MessageHandler { get; } = new BitgetRestMessageHandler(BitgetErrors.RestErrors);

internal BitgetRestClient _baseClient;

internal BitgetRestClientSpotApi(ILoggerFactory? loggerFactory, HttpClient? httpClient, BitgetRestClient baseClient, BitgetRestOptions options)
: base(loggerFactory, BitgetExchange.Metadata.Id, httpClient, options.Environment.RestBaseAddress, options, options.SpotOptions)
{
_baseClient = baseClient;

Account = new BitgetRestClientSpotApiAccount(this);
Margin = new BitgetRestClientSpotApiMargin(this);
ExchangeData = new BitgetRestClientSpotApiExchangeData(this);
Expand Down
64 changes: 58 additions & 6 deletions Bitget.Net/Clients/SpotApiV2/BitgetRestClientSpotApiShared.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Bitget.Net.Enums;
using Bitget.Net.Enums.V2;
using Bitget.Net.Interfaces.Clients.SpotApiV2;
using Bitget.Net.Objects.Models;
using Bitget.Net.Objects.Models.V2;
using CryptoExchange.Net;
using CryptoExchange.Net.Objects;
Expand All @@ -18,6 +19,7 @@ internal partial class BitgetRestClientSpotApi : IBitgetRestClientSpotApiShared
public void SetDefaultExchangeParameter(string key, object value) => ExchangeParameters.SetStaticParameter(Exchange, key, value);
public void ResetDefaultExchangeParameters() => ExchangeParameters.ResetStaticParameters();
public SharedClientInfo Discover() => SharedUtils.GetClientInfo(BitgetExchange.Metadata, this);
private static HashSet<string> _exchangeSupportedFiatCurrencies = ["EUR", "USD", "BRL"];

#region Kline client

Expand Down Expand Up @@ -83,6 +85,7 @@ async Task<HttpResult<SharedKline[]>> IKlineRestClient.GetKlinesAsync(GetKlinesR
#endregion

#region Spot Symbol client
SharedSymbolCatalog? ISpotSymbolRestClient.SpotSymbolCatalog => ExchangeSymbolCache.GetSymbolCatalog(_exchangeName, _topicId, EnvironmentName, null);
GetSpotSymbolsOptions ISpotSymbolRestClient.GetSpotSymbolsOptions { get; } = new GetSpotSymbolsOptions(_exchangeName, false);

async Task<HttpResult<SharedSpotSymbol[]>> ISpotSymbolRestClient.GetSpotSymbolsAsync(GetSymbolsRequest request, CancellationToken ct)
Expand All @@ -91,21 +94,70 @@ async Task<HttpResult<SharedSpotSymbol[]>> ISpotSymbolRestClient.GetSpotSymbolsA
if (validationError != null)
return HttpResult.Fail<SharedSpotSymbol[]>(Exchange, validationError);

var result = await ExchangeData.GetSymbolsAsync(ct: ct).ConfigureAwait(false);
var result = await _baseClient.UnifiedApi.ExchangeData.GetSpotSymbolsAsync(ct: ct).ConfigureAwait(false);
if (!result.Success)
return HttpResult.Fail<SharedSpotSymbol[]>(result);

var response = HttpResult.Ok(result, result.Data.Select(s => new SharedSpotSymbol(s.BaseAsset, s.QuoteAsset, s.Symbol, s.Status == Enums.SymbolStatus.Online)
var data = result.Data
.Select(x => ParseSymbol(x)!)
.Where(x => x != null)
.ToArray();

ExchangeSymbolCache.UpdateSymbolInfo(_topicId, EnvironmentName, null, data);
return HttpResult.Ok(result, SharedUtils.ApplySymbolFilter(data, request));
}

private SharedSpotSymbol ParseSymbol(BitgetUaSpotSymbol s)
{
var result = new SharedSpotSymbol(s.BaseAsset, s.QuoteAsset, s.Symbol, s.Status == Enums.Uta.InstrumentStatus.Online)
{
MinTradeQuantity = s.MinOrderQuantity,
MinNotionalValue = s.MinOrderValue,
MaxTradeQuantity = s.MaxOrderQuantity,
QuantityDecimals = s.QuantityPrecision,
PriceDecimals = s.PricePrecision
}).ToArray());
PriceDecimals = s.PricePrecision,
DisplayName = s.Symbol
};

if (s.SymbolType == Enums.Uta.SymbolType.Commodity || s.SymbolType == Enums.Uta.SymbolType.Metal)
{
result.BaseAssetType = SharedAssetType.TradFi;
result.BaseAssetSubType = SharedAssetSubType.Commodity;
}
else if (s.SymbolType == Enums.Uta.SymbolType.Stock)
{
result.BaseAssetType = SharedAssetType.TradFi;
result.BaseAssetSubType = SharedAssetSubType.Equity;
}
else if (LibraryHelpers.IsStableCoin(s.BaseAsset))
{
result.BaseAssetType = SharedAssetType.Crypto;
result.BaseAssetSubType = SharedAssetSubType.StableCoin;
}
else if (_exchangeSupportedFiatCurrencies.Contains(s.BaseAsset))
{
result.BaseAssetType = SharedAssetType.Fiat;
}
else
{
result.BaseAssetType = SharedAssetType.Crypto;
}

if (LibraryHelpers.IsStableCoin(s.QuoteAsset))
{
result.QuoteAssetType = SharedAssetType.Crypto;
result.QuoteAssetSubType = SharedAssetSubType.StableCoin;
}
else if (_exchangeSupportedFiatCurrencies.Contains(s.QuoteAsset))
{
result.QuoteAssetType = SharedAssetType.Fiat;
}
else
{
result.QuoteAssetType = SharedAssetType.Crypto;
}

ExchangeSymbolCache.UpdateSymbolInfo(_topicId, EnvironmentName, null, response.Data!);
return response;
return result;
}

async Task<ExchangeCallResult<SharedSymbol[]>> ISpotSymbolRestClient.GetSpotSymbolsForBaseAssetAsync(string baseAsset)
Expand Down
Loading