diff --git a/Alpaca.Markets.Tests/AlpacaOptionsDataClientTest.Snapshots.cs b/Alpaca.Markets.Tests/AlpacaOptionsDataClientTest.Snapshots.cs index 59bed032..f46a0b84 100644 --- a/Alpaca.Markets.Tests/AlpacaOptionsDataClientTest.Snapshots.cs +++ b/Alpaca.Markets.Tests/AlpacaOptionsDataClientTest.Snapshots.cs @@ -8,7 +8,7 @@ public async Task ListSnapshotsAsyncWorks() using var mock = mockClientsFactory.GetAlpacaOptionsDataClientMock(); // TODO: olegra - create special method for option snapshots - mock.AddCryptoSnapshotsExpectation(PathPrefix, _symbols); + mock.AddOptionSnapshotsExpectation(PathPrefix, _symbols); var snapshots = await mock.Client.ListSnapshotsAsync( new OptionSnapshotRequest(_symbols)); @@ -58,6 +58,14 @@ private static void validate( Assert.True(snapshot.Trade.Validate(symbol)); Assert.True(snapshot.Quote.Validate(symbol)); - // TODO: add validation for greeks and IV + Assert.NotNull(snapshot.ImpliedVolatility); + Assert.True(snapshot.ImpliedVolatility > 0.0M); + + Assert.NotNull(snapshot.Greeks); + Assert.NotNull(snapshot.Greeks.Delta); + Assert.NotNull(snapshot.Greeks.Gamma); + Assert.NotNull(snapshot.Greeks.Theta); + Assert.NotNull(snapshot.Greeks.Vega); + Assert.NotNull(snapshot.Greeks.Rho); } } diff --git a/Alpaca.Markets.Tests/HistoricalDataHelpers.cs b/Alpaca.Markets.Tests/HistoricalDataHelpers.cs index e625f05a..e2635870 100644 --- a/Alpaca.Markets.Tests/HistoricalDataHelpers.cs +++ b/Alpaca.Markets.Tests/HistoricalDataHelpers.cs @@ -6,6 +6,7 @@ namespace Alpaca.Markets.Tests; [SuppressMessage("ReSharper", "ParameterOnlyUsedForPreconditionCheck.Global")] [SuppressMessage("ReSharper", "ParameterOnlyUsedForPreconditionCheck.Local")] #pragma warning restore IDE0079 // Remove unnecessary suppression +[SuppressMessage("Usage", "xUnit1047:Avoid using TheoryDataRow arguments that might not be serializable")] internal static class HistoricalDataHelpers { private static readonly String _condition = Guid.NewGuid().ToString("D"); @@ -34,6 +35,8 @@ internal static class HistoricalDataHelpers private const Decimal Volume = 1_000M; + private const Decimal Greeks = 0.01M; + private const String Bars = "bars"; private const Decimal Size = 42M; @@ -113,11 +116,17 @@ internal static void AddCryptoSnapshotsExpectation( new JProperty("snapshots", new JObject( symbols.Select(name => new JProperty(name, createSnapshot())))))); + internal static void AddOptionSnapshotsExpectation( + this IMock mock, String pathPrefix, IEnumerable symbols) => + mock.AddGet($"{pathPrefix}/snapshots", new JObject( + new JProperty("snapshots", new JObject( + symbols.Select(name => new JProperty(name, createOptionSnapshot())))))); + internal static void AddOptionChainExpectation( this IMock mock, String pathPrefix, IEnumerable symbols) => mock.AddGet($"{pathPrefix}/snapshots/*", new JObject( new JProperty("snapshots", new JObject( - symbols.Select(name => new JProperty(name, createSnapshot())))))); + symbols.Select(name => new JProperty(name, createOptionSnapshot())))))); internal static void AddOrderBooksExpectation( this IMock mock, String pathPrefix, IEnumerable symbols) => @@ -439,4 +448,20 @@ private static JObject createSnapshot( new JProperty("minuteBar", CreateBar()), new JProperty("dailyBar", CreateBar()), new JProperty("symbol", symbol)); + + private static JObject createOptionSnapshot() => + new( + new JProperty("latestQuote", CreateQuote()), + new JProperty("latestTrade", CreateTrade()), + new JProperty("impliedVolatility", Volume), + new JProperty("greeks", createGreeks()), + new JProperty("symbol", String.Empty)); + + private static JObject createGreeks() => + new( + new JProperty("delta", Greeks), + new JProperty("gamma", Greeks), + new JProperty("theta", Greeks), + new JProperty("vega", Greeks), + new JProperty("rho", Greeks)); } diff --git a/Alpaca.Markets.Tests/RequestValidationTest.cs b/Alpaca.Markets.Tests/RequestValidationTest.cs index e51b6aad..036e95fe 100644 --- a/Alpaca.Markets.Tests/RequestValidationTest.cs +++ b/Alpaca.Markets.Tests/RequestValidationTest.cs @@ -99,6 +99,24 @@ public void PortfolioHistoryRequestEmptySymbolValidationWorks() => } .WithInterval(new Interval(DateTime.Today, DateTime.Today))); + [Fact] + public void OptionContactsRequestExpirationDateValidationWorks() => + validate(new OptionContractsRequest + { + ExpirationDateGreaterThanOrEqualTo = DateOnly.FromDateTime(DateTime.Today), + ExpirationDateLessThanOrEqualTo = DateOnly.FromDateTime(DateTime.Today), + ExpirationDateEqualTo = DateOnly.FromDateTime(DateTime.Today), + }); + + [Fact] + public void OptionChainRequestExpirationDateValidationWorks() => + validate(new OptionChainRequest(Symbol) + { + ExpirationDateGreaterThanOrEqualTo = DateOnly.FromDateTime(DateTime.Today), + ExpirationDateLessThanOrEqualTo = DateOnly.FromDateTime(DateTime.Today), + ExpirationDateEqualTo = DateOnly.FromDateTime(DateTime.Today), + }); + [Fact] public void CalendarRequestConstructorWorks() { @@ -134,7 +152,7 @@ public void ListOrdersRequestWithSymbolsWorks() Assert.NotNull(request.Symbols); Assert.Empty(request.Symbols); - request.WithSymbols(new[] { Symbol }); + request.WithSymbols([Symbol]); Assert.NotNull(request.Symbols); Assert.NotEmpty(request.Symbols); diff --git a/Alpaca.Markets/AlpacaOptionsDataClient.cs b/Alpaca.Markets/AlpacaOptionsDataClient.cs index a6253711..eb86fcd4 100644 --- a/Alpaca.Markets/AlpacaOptionsDataClient.cs +++ b/Alpaca.Markets/AlpacaOptionsDataClient.cs @@ -38,14 +38,14 @@ public async Task> ListSnapshotsAsync( OptionSnapshotRequest request, CancellationToken cancellationToken = default) => await HttpClient.GetAsync, JsonOptionsSnapshotData>( - await request.GetUriBuilderAsync(HttpClient).ConfigureAwait(false), + await request.EnsureNotNull().Validate().GetUriBuilderAsync(HttpClient).ConfigureAwait(false), RateLimitHandler, cancellationToken).ConfigureAwait(false); public async Task> GetOptionChainAsync( OptionChainRequest request, CancellationToken cancellationToken = default) => await HttpClient.GetAsync, JsonOptionsSnapshotData>( - await request.GetUriBuilderAsync(HttpClient).ConfigureAwait(false), + await request.EnsureNotNull().Validate().GetUriBuilderAsync(HttpClient).ConfigureAwait(false), RateLimitHandler, cancellationToken).ConfigureAwait(false); private async Task> getLatestAsync( diff --git a/Alpaca.Markets/Parameters/OptionSnapshotRequest.cs b/Alpaca.Markets/Parameters/OptionSnapshotRequest.cs index e93984e0..6bb41ae6 100644 --- a/Alpaca.Markets/Parameters/OptionSnapshotRequest.cs +++ b/Alpaca.Markets/Parameters/OptionSnapshotRequest.cs @@ -31,11 +31,17 @@ public OptionSnapshotRequest( [ExcludeFromCodeCoverage] public OptionsFeed? OptionsFeed { get; set; } + /// + /// Gets the pagination parameters for the request (page size and token). + /// + [UsedImplicitly] + public Pagination Pagination { get; } = new(); + internal async ValueTask GetUriBuilderAsync( HttpClient httpClient) => new UriBuilder(httpClient.BaseAddress!) { - Query = await new QueryBuilder() + Query = await Pagination.QueryBuilder .AddParameter("symbols", Symbols.ToList()) .AddParameter("feed", OptionsFeed) .AsStringAsync().ConfigureAwait(false) diff --git a/Alpaca.Markets/PublicAPI.Shipped.txt b/Alpaca.Markets/PublicAPI.Shipped.txt index c8765edb..8598c6b6 100644 --- a/Alpaca.Markets/PublicAPI.Shipped.txt +++ b/Alpaca.Markets/PublicAPI.Shipped.txt @@ -1101,6 +1101,7 @@ Alpaca.Markets.OptionSnapshotRequest Alpaca.Markets.OptionSnapshotRequest.OptionsFeed.get -> Alpaca.Markets.OptionsFeed? Alpaca.Markets.OptionSnapshotRequest.OptionsFeed.set -> void Alpaca.Markets.OptionSnapshotRequest.OptionSnapshotRequest(System.Collections.Generic.IEnumerable! symbols) -> void +Alpaca.Markets.OptionSnapshotRequest.Pagination.get -> Alpaca.Markets.Pagination! Alpaca.Markets.OptionSnapshotRequest.Symbols.get -> System.Collections.Generic.IReadOnlyCollection! Alpaca.Markets.OptionStyle Alpaca.Markets.OptionStyle.American = 0 -> Alpaca.Markets.OptionStyle