diff --git a/GDAXClient.Specs/GDAXClient.Specs.csproj b/GDAXClient.Specs/GDAXClient.Specs.csproj index 61866fad..c127bc0c 100644 --- a/GDAXClient.Specs/GDAXClient.Specs.csproj +++ b/GDAXClient.Specs/GDAXClient.Specs.csproj @@ -84,6 +84,10 @@ + + + + @@ -94,6 +98,7 @@ + diff --git a/GDAXClient.Specs/JsonFixtures/Deposits/CoinbaseDepositResponseFixture.cs b/GDAXClient.Specs/JsonFixtures/Deposits/CoinbaseDepositResponseFixture.cs index 537a1127..786a86f7 100644 --- a/GDAXClient.Specs/JsonFixtures/Deposits/CoinbaseDepositResponseFixture.cs +++ b/GDAXClient.Specs/JsonFixtures/Deposits/CoinbaseDepositResponseFixture.cs @@ -1,6 +1,6 @@ namespace GDAXClient.Specs.JsonFixtures.Deposits { - class CoinbaseDepositResponseFixture + public static class CoinbaseDepositResponseFixture { public static string Create() { diff --git a/GDAXClient.Specs/JsonFixtures/Products/ProductStatsFixture.cs b/GDAXClient.Specs/JsonFixtures/Products/ProductStatsFixture.cs new file mode 100644 index 00000000..643b8cfd --- /dev/null +++ b/GDAXClient.Specs/JsonFixtures/Products/ProductStatsFixture.cs @@ -0,0 +1,19 @@ +namespace GDAXClient.Specs.JsonFixtures.Products +{ + public static class ProductStatsFixture + { + public static string Create() + { + + var json = @" +{ + 'open': '34.19000000', + 'high': '95.70000000', + 'low': '7.06000000', + 'volume': '2.41000000' +}"; + + return json; + } + } +} diff --git a/GDAXClient.Specs/JsonFixtures/Products/ProductTickerFixture.cs b/GDAXClient.Specs/JsonFixtures/Products/ProductTickerFixture.cs new file mode 100644 index 00000000..08eaca8e --- /dev/null +++ b/GDAXClient.Specs/JsonFixtures/Products/ProductTickerFixture.cs @@ -0,0 +1,21 @@ +namespace GDAXClient.Specs.JsonFixtures.Products +{ + public static class ProductTickerFixture + { + public static string Create() + { + var json = @" +{ + 'trade_id': 4729088, + 'price': '333.99', + 'size': '0.193', + 'bid': '333.98', + 'ask': '333.99', + 'volume': '5957.11914015', + 'time': '2016-12-08T24:00:00Z' +}"; + + return json; + } + } +} diff --git a/GDAXClient.Specs/JsonFixtures/Products/ProductsOrderBookResponseFixture.cs b/GDAXClient.Specs/JsonFixtures/Products/ProductsOrderBookResponseFixture.cs new file mode 100644 index 00000000..55ae3ee1 --- /dev/null +++ b/GDAXClient.Specs/JsonFixtures/Products/ProductsOrderBookResponseFixture.cs @@ -0,0 +1,21 @@ +namespace GDAXClient.Specs.JsonFixtures.Products +{ + public static class ProductsOrderBookResponseFixture + { + public static string Create() + { + var json = @" +{ + 'sequence': '3', + 'bids': [ + [200, 100, 3], + ], + 'ask': [ + [200, 100, 3], + ] +}"; + + return json; + } + } +} diff --git a/GDAXClient.Specs/JsonFixtures/Products/ProductsResponseFixture.cs b/GDAXClient.Specs/JsonFixtures/Products/ProductsResponseFixture.cs new file mode 100644 index 00000000..48d0f30b --- /dev/null +++ b/GDAXClient.Specs/JsonFixtures/Products/ProductsResponseFixture.cs @@ -0,0 +1,22 @@ +namespace GDAXClient.Specs.JsonFixtures.Products +{ + public static class ProductsResponseFixture + { + public static string Create() + { + var json = @" +[ + { + 'id': 'BTC-USD', + 'base_currency': 'BTC', + 'quote_currency': 'USD', + 'base_min_size': '0.01', + 'base_max_size': '10000.00', + 'quote_increment': '0.01' + } +]"; + + return json; + } + } +} diff --git a/GDAXClient.Specs/Services/Products/ProductsServiceSpecs.cs b/GDAXClient.Specs/Services/Products/ProductsServiceSpecs.cs new file mode 100644 index 00000000..3a9a9c0e --- /dev/null +++ b/GDAXClient.Specs/Services/Products/ProductsServiceSpecs.cs @@ -0,0 +1,150 @@ +using GDAXClient.Authentication; +using GDAXClient.HttpClient; +using GDAXClient.Products; +using GDAXClient.Services.HttpRequest; +using GDAXClient.Services.Orders; +using GDAXClient.Services.Products; +using GDAXClient.Services.Products.Models; +using GDAXClient.Services.Products.Models.Responses; +using GDAXClient.Specs.JsonFixtures.Products; +using Machine.Fakes; +using Machine.Specifications; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; + +namespace GDAXClient.Specs.Services.Payments +{ + [Subject("ProductsService")] + public class ProductsServiceSpecs : WithSubject + { + static Authenticator authenticator; + + static IEnumerable products_result; + + static ProductsOrderBookResponse product_order_books_response; + + static ProductTicker product_ticker_result; + + static ProductStats product_stats_result; + + Establish context = () => + authenticator = new Authenticator("apiKey", new string('2', 100), "passPhrase"); + + class when_getting_all_products + { + Establish context = () => + { + The().WhenToldTo(p => p.CreateHttpRequestMessage(Param.IsAny(), Param.IsAny(), Param.IsAny(), Param.IsAny())) + .Return(new HttpRequestMessage()); + + The().WhenToldTo(p => p.SendASync(Param.IsAny())) + .Return(Task.FromResult(new HttpResponseMessage())); + + The().WhenToldTo(p => p.ReadAsStringAsync(Param.IsAny())) + .Return(Task.FromResult(ProductsResponseFixture.Create())); + }; + + Because of = () => + products_result = Subject.GetAllProductsAsync().Result; + + It should_have_correct_products_response_count = () => + products_result.Count().ShouldEqual(1); + + It should_have_correct_products = () => + { + products_result.First().Id.ShouldEqual("BTC-USD"); + products_result.First().Base_currency.ShouldEqual("BTC"); + products_result.First().Quote_currency.ShouldEqual("USD"); + products_result.First().Base_min_size.ShouldEqual("0.01"); + products_result.First().Base_max_size.ShouldEqual("10000.00"); + products_result.First().Quote_increment.ShouldEqual("0.01"); + }; + } + + class when_getting_a_product_order_book + { + Establish context = () => + { + The().WhenToldTo(p => p.CreateHttpRequestMessage(Param.IsAny(), Param.IsAny(), Param.IsAny(), Param.IsAny())) + .Return(new HttpRequestMessage()); + + The().WhenToldTo(p => p.SendASync(Param.IsAny())) + .Return(Task.FromResult(new HttpResponseMessage())); + + The().WhenToldTo(p => p.ReadAsStringAsync(Param.IsAny())) + .Return(Task.FromResult(ProductsOrderBookResponseFixture.Create())); + }; + + Because of = () => + product_order_books_response = Subject.GetProductOrderBookAsync(ProductType.BtcUsd).Result; + + It should_have_correct_product_order_book_response = () => + { + product_order_books_response.Sequence.ShouldEqual(3M); + product_order_books_response.Bids.SelectMany(p => p).ShouldContain(200); + product_order_books_response.Bids.SelectMany(p => p).ShouldContain(100); + product_order_books_response.Bids.SelectMany(p => p).ShouldContain(3); + product_order_books_response.Ask.SelectMany(p => p).ShouldContain(200); + product_order_books_response.Ask.SelectMany(p => p).ShouldContain(100); + product_order_books_response.Ask.SelectMany(p => p).ShouldContain(3); + }; + } + + class when_getting_a_product_ticker + { + Establish context = () => + { + The().WhenToldTo(p => p.CreateHttpRequestMessage(Param.IsAny(), Param.IsAny(), Param.IsAny(), Param.IsAny())) + .Return(new HttpRequestMessage()); + + The().WhenToldTo(p => p.SendASync(Param.IsAny())) + .Return(Task.FromResult(new HttpResponseMessage())); + + The().WhenToldTo(p => p.ReadAsStringAsync(Param.IsAny())) + .Return(Task.FromResult(ProductTickerFixture.Create())); + }; + + Because of = () => + product_ticker_result = Subject.GetProductTickerAsync(ProductType.BtcUsd).Result; + + It should_have_correct_product_ticker = () => + { + product_ticker_result.Trade_id.ShouldEqual(4729088); + product_ticker_result.Price.ShouldEqual(333.99M); + product_ticker_result.Size.ShouldEqual(0.193M); + product_ticker_result.Bid.ShouldEqual(333.98M); + product_ticker_result.Ask.ShouldEqual(333.99M); + product_ticker_result.Volume.ShouldEqual(5957.11914015M); + product_ticker_result.Time.ShouldEqual(new System.DateTime(2016, 12, 9)); + }; + } + + class when_getting_product_stats + { + Establish context = () => + { + The().WhenToldTo(p => p.CreateHttpRequestMessage(Param.IsAny(), Param.IsAny(), Param.IsAny(), Param.IsAny())) + .Return(new HttpRequestMessage()); + + The().WhenToldTo(p => p.SendASync(Param.IsAny())) + .Return(Task.FromResult(new HttpResponseMessage())); + + The().WhenToldTo(p => p.ReadAsStringAsync(Param.IsAny())) + .Return(Task.FromResult(ProductStatsFixture.Create())); + }; + + Because of = () => + product_stats_result = Subject.GetProductStatsAsync(ProductType.BtcUsd).Result; + + It should_have_correct_product_stats = () => + { + product_stats_result.Open.ShouldEqual(34.19000000M); + product_stats_result.High.ShouldEqual(95.70000000M); + product_stats_result.Low.ShouldEqual(7.06000000M); + product_stats_result.Volume.ShouldEqual(2.41000000M); + }; + } + } +} diff --git a/GDAXClient/GDAXClient.cs b/GDAXClient/GDAXClient.cs index 3cc4e03d..285717c0 100644 --- a/GDAXClient/GDAXClient.cs +++ b/GDAXClient/GDAXClient.cs @@ -1,4 +1,5 @@ using GDAXClient.Authentication; +using GDAXClient.Products; using GDAXClient.Services.Accounts; using GDAXClient.Services.CoinbaseAccounts; using GDAXClient.Services.Deposits; @@ -27,6 +28,7 @@ public GDAXClient(Authenticator authenticator, bool sandBox = false) PaymentsService = new PaymentsService(httpClient, httpRequestMessageService, authenticator); WithdrawalsService = new WithdrawalsService(httpClient, httpRequestMessageService, authenticator); DepositsService = new DepositsService(httpClient, httpRequestMessageService, authenticator); + ProductsService = new ProductsService(httpClient, httpRequestMessageService, authenticator); } public AccountsService AccountsService { get; } @@ -40,5 +42,7 @@ public GDAXClient(Authenticator authenticator, bool sandBox = false) public WithdrawalsService WithdrawalsService { get; } public DepositsService DepositsService { get; } + + public ProductsService ProductsService { get; } } } diff --git a/GDAXClient/GDAXClient.csproj b/GDAXClient/GDAXClient.csproj index e6002553..7facd1c7 100644 --- a/GDAXClient/GDAXClient.csproj +++ b/GDAXClient/GDAXClient.csproj @@ -55,34 +55,39 @@ + + + + + - + - + - - - + + + - - - - + + + + - - + + - - - - - - + + + + + + @@ -95,5 +100,6 @@ + \ No newline at end of file diff --git a/GDAXClient/Services/Accounts/Account.cs b/GDAXClient/Services/Accounts/Models/Account.cs similarity index 100% rename from GDAXClient/Services/Accounts/Account.cs rename to GDAXClient/Services/Accounts/Models/Account.cs diff --git a/GDAXClient/Services/CoinbaseAccounts/CoinbaseAccount.cs b/GDAXClient/Services/CoinbaseAccounts/Models/CoinbaseAccount.cs similarity index 100% rename from GDAXClient/Services/CoinbaseAccounts/CoinbaseAccount.cs rename to GDAXClient/Services/CoinbaseAccounts/Models/CoinbaseAccount.cs diff --git a/GDAXClient/Services/Deposits/Deposit.cs b/GDAXClient/Services/Deposits/Models/Deposit.cs similarity index 100% rename from GDAXClient/Services/Deposits/Deposit.cs rename to GDAXClient/Services/Deposits/Models/Deposit.cs diff --git a/GDAXClient/Services/Deposits/DepositResponse.cs b/GDAXClient/Services/Deposits/Models/Responses/DepositResponse.cs similarity index 100% rename from GDAXClient/Services/Deposits/DepositResponse.cs rename to GDAXClient/Services/Deposits/Models/Responses/DepositResponse.cs diff --git a/GDAXClient/Services/Orders/Order.cs b/GDAXClient/Services/Orders/Models/Order.cs similarity index 100% rename from GDAXClient/Services/Orders/Order.cs rename to GDAXClient/Services/Orders/Models/Order.cs diff --git a/GDAXClient/Services/Orders/OrderSide.cs b/GDAXClient/Services/Orders/Models/OrderSide.cs similarity index 100% rename from GDAXClient/Services/Orders/OrderSide.cs rename to GDAXClient/Services/Orders/Models/OrderSide.cs diff --git a/GDAXClient/Services/Orders/CancelOrderResponse.cs b/GDAXClient/Services/Orders/Models/Responses/CancelOrderResponse.cs similarity index 100% rename from GDAXClient/Services/Orders/CancelOrderResponse.cs rename to GDAXClient/Services/Orders/Models/Responses/CancelOrderResponse.cs diff --git a/GDAXClient/Services/Orders/OrderResponse.cs b/GDAXClient/Services/Orders/Models/Responses/OrderResponse.cs similarity index 100% rename from GDAXClient/Services/Orders/OrderResponse.cs rename to GDAXClient/Services/Orders/Models/Responses/OrderResponse.cs diff --git a/GDAXClient/Services/Payments/PaymentMethod.cs b/GDAXClient/Services/Payments/Models/PaymentMethod.cs similarity index 100% rename from GDAXClient/Services/Payments/PaymentMethod.cs rename to GDAXClient/Services/Payments/Models/PaymentMethod.cs diff --git a/GDAXClient/Services/Products/Models/Product.cs b/GDAXClient/Services/Products/Models/Product.cs new file mode 100644 index 00000000..1bc07607 --- /dev/null +++ b/GDAXClient/Services/Products/Models/Product.cs @@ -0,0 +1,17 @@ +namespace GDAXClient.Services.Products +{ + public class Product + { + public string Id { get; set; } + + public string Base_currency { get; set; } + + public string Quote_currency { get; set; } + + public string Base_min_size { get; set; } + + public string Base_max_size { get; set; } + + public string Quote_increment { get; set; } + } +} diff --git a/GDAXClient/Services/Products/Models/ProductStats.cs b/GDAXClient/Services/Products/Models/ProductStats.cs new file mode 100644 index 00000000..777357b9 --- /dev/null +++ b/GDAXClient/Services/Products/Models/ProductStats.cs @@ -0,0 +1,13 @@ +namespace GDAXClient.Services.Products.Models +{ + public class ProductStats + { + public decimal Open { get; set; } + + public decimal High { get; set; } + + public decimal Low { get; set; } + + public decimal Volume { get; set; } + } +} diff --git a/GDAXClient/Services/Products/Models/ProductTicker.cs b/GDAXClient/Services/Products/Models/ProductTicker.cs new file mode 100644 index 00000000..56d20c3d --- /dev/null +++ b/GDAXClient/Services/Products/Models/ProductTicker.cs @@ -0,0 +1,21 @@ +using System; + +namespace GDAXClient.Services.Products.Models +{ + public class ProductTicker + { + public int Trade_id { get; set; } + + public decimal Price { get; set; } + + public decimal Size { get; set; } + + public decimal Bid { get; set; } + + public decimal Ask { get; set; } + + public decimal Volume { get; set; } + + public DateTime Time { get; set; } + } +} diff --git a/GDAXClient/Services/Products/Models/Responses/ProductsOrderBookResponse.cs b/GDAXClient/Services/Products/Models/Responses/ProductsOrderBookResponse.cs new file mode 100644 index 00000000..d2e67633 --- /dev/null +++ b/GDAXClient/Services/Products/Models/Responses/ProductsOrderBookResponse.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +namespace GDAXClient.Services.Products.Models.Responses +{ + public class ProductsOrderBookResponse + { + public decimal Sequence { get; set; } + + public IEnumerable> Bids { get; set; } + + public IEnumerable> Ask { get; set; } + } +} diff --git a/GDAXClient/Services/Products/ProductsService.cs b/GDAXClient/Services/Products/ProductsService.cs new file mode 100644 index 00000000..800bb47e --- /dev/null +++ b/GDAXClient/Services/Products/ProductsService.cs @@ -0,0 +1,68 @@ +using GDAXClient.HttpClient; +using GDAXClient.Services; +using GDAXClient.Services.Accounts; +using GDAXClient.Services.HttpRequest; +using GDAXClient.Services.Orders; +using GDAXClient.Services.Products; +using GDAXClient.Services.Products.Models; +using GDAXClient.Services.Products.Models.Responses; +using GDAXClient.Utilities.Extensions; +using Newtonsoft.Json; +using System.Collections.Generic; +using System.Net.Http; +using System.Threading.Tasks; + +namespace GDAXClient.Products +{ + public class ProductsService : AbstractService + { + private readonly IHttpRequestMessageService httpRequestMessageService; + + private readonly IHttpClient httpClient; + + private readonly IAuthenticator authenticator; + + public ProductsService( + IHttpClient httpClient, + IHttpRequestMessageService httpRequestMessageService, + IAuthenticator authenticator) + : base(httpClient, httpRequestMessageService, authenticator) + { + this.httpRequestMessageService = httpRequestMessageService; + this.httpClient = httpClient; + this.authenticator = authenticator; + } + + public async Task> GetAllProductsAsync() + { + var contentBody = await SendHttpRequestMessage(HttpMethod.Get, authenticator, "/products"); + var productsResponse = JsonConvert.DeserializeObject>(contentBody); + + return productsResponse; + } + + public async Task GetProductOrderBookAsync(ProductType productPair) + { + var contentBody = await SendHttpRequestMessage(HttpMethod.Get, authenticator, $"/products/{productPair.ToDasherizedUpper()}/book"); + var productOrderBookResponse = JsonConvert.DeserializeObject(contentBody); + + return productOrderBookResponse; + } + + public async Task GetProductTickerAsync(ProductType productPair) + { + var contentBody = await SendHttpRequestMessage(HttpMethod.Get, authenticator, $"/products/{productPair.ToDasherizedUpper()}/ticker"); + var productTickerResponse = JsonConvert.DeserializeObject(contentBody); + + return productTickerResponse; + } + + public async Task GetProductStatsAsync(ProductType productPair) + { + var contentBody = await SendHttpRequestMessage(HttpMethod.Get, authenticator, $"/products/{productPair.ToDasherizedUpper()}/stats"); + var productStatsResponse = JsonConvert.DeserializeObject(contentBody); + + return productStatsResponse; + } + } +} diff --git a/GDAXClient/Services/Withdrawals/Coinbase.cs b/GDAXClient/Services/Withdrawals/Models/Coinbase.cs similarity index 100% rename from GDAXClient/Services/Withdrawals/Coinbase.cs rename to GDAXClient/Services/Withdrawals/Models/Coinbase.cs diff --git a/GDAXClient/Services/Withdrawals/Crypto.cs b/GDAXClient/Services/Withdrawals/Models/Crypto.cs similarity index 100% rename from GDAXClient/Services/Withdrawals/Crypto.cs rename to GDAXClient/Services/Withdrawals/Models/Crypto.cs diff --git a/GDAXClient/Services/Withdrawals/CoinbaseResponse.cs b/GDAXClient/Services/Withdrawals/Models/Responses/CoinbaseResponse.cs similarity index 100% rename from GDAXClient/Services/Withdrawals/CoinbaseResponse.cs rename to GDAXClient/Services/Withdrawals/Models/Responses/CoinbaseResponse.cs diff --git a/GDAXClient/Services/Withdrawals/CryptoResponse.cs b/GDAXClient/Services/Withdrawals/Models/Responses/CryptoResponse.cs similarity index 100% rename from GDAXClient/Services/Withdrawals/CryptoResponse.cs rename to GDAXClient/Services/Withdrawals/Models/Responses/CryptoResponse.cs diff --git a/GDAXClient/Services/Withdrawals/WithdrawalResponse.cs b/GDAXClient/Services/Withdrawals/Models/Responses/WithdrawalResponse.cs similarity index 100% rename from GDAXClient/Services/Withdrawals/WithdrawalResponse.cs rename to GDAXClient/Services/Withdrawals/Models/Responses/WithdrawalResponse.cs diff --git a/GDAXClient/Services/Withdrawals/Withdrawal.cs b/GDAXClient/Services/Withdrawals/Models/Withdrawal.cs similarity index 100% rename from GDAXClient/Services/Withdrawals/Withdrawal.cs rename to GDAXClient/Services/Withdrawals/Models/Withdrawal.cs diff --git a/GDAXClient/Services/Currency.cs b/GDAXClient/Shared/Currency.cs similarity index 100% rename from GDAXClient/Services/Currency.cs rename to GDAXClient/Shared/Currency.cs diff --git a/GDAXClient/Services/Orders/ProductType.cs b/GDAXClient/Shared/ProductType.cs similarity index 51% rename from GDAXClient/Services/Orders/ProductType.cs rename to GDAXClient/Shared/ProductType.cs index 54c1d735..a39f307e 100644 --- a/GDAXClient/Services/Orders/ProductType.cs +++ b/GDAXClient/Shared/ProductType.cs @@ -3,7 +3,13 @@ public enum ProductType { BtcUsd, + BtcEur, + BtcGbp, EthUsd, - LtcUsd + EthEur, + EthBtc, + LtcUsd, + LtcEur, + LtcBtc } } diff --git a/README.md b/README.md index e22a811c..9d30561a 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,12 @@ var allAccounts = await gdaxClient.AccountsService.GetAllAccountsAsync(); - DepositFundsAsync(paymentMethodId, amount, currency) - deposits funds from a payment method - DepositCoinbaseFundsAsync(coinbaseAccountId, amount, currency) - deposits funds from a coinbase account +###### Products ###### +- GetAllProductsAsync() - get a list of available currency pairs for trading +- GetProductOrderBookAsync(productType) - get a list of open orders for a product +- GetProductTickerAsync(productType) - get information about the last trade (tick), best bid/ask and 24h volume +- GetProductStatsAsync(productType) - get 24 hour stats for a product +

Sandbox Support

Generate your key at https://public.sandbox.gdax.com/settings/api