Skip to content

Commit

Permalink
Issue #472 - Added support for the new portfolio history endpoint usi…
Browse files Browse the repository at this point in the history
…ng the existing `IAlpacaTradingClient.GetPortfolioHistoryAsync` method.

(cherry picked from commit 361e0f0)
  • Loading branch information
OlegRa committed Apr 14, 2024
1 parent 920b300 commit 988ead9
Show file tree
Hide file tree
Showing 11 changed files with 128 additions and 41 deletions.
7 changes: 2 additions & 5 deletions Alpaca.Markets.Tests/AlpacaTradingClientTest.Account.cs
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,6 @@ public async Task GetPortfolioHistoryAsyncWorks()
using var mock = mockClientsFactory.GetAlpacaTradingClientMock();

var today = DateTime.UtcNow.Date;
var todayDateOnly = DateOnly.FromDateTime(today);

mock.AddGet("/v2/account/portfolio/history", new JObject(
new JProperty("timestamp", new JArray(
Expand All @@ -180,10 +179,8 @@ public async Task GetPortfolioHistoryAsyncWorks()
var history = await mock.Client.GetPortfolioHistoryAsync(
new PortfolioHistoryRequest
{
Period = new HistoryPeriod(5, HistoryPeriodUnit.Day),
TimeFrame = TimeFrame.FifteenMinutes,
ExtendedHours = true
}.WithInterval(new Interval<DateOnly>(todayDateOnly, todayDateOnly)));
TimeFrame = TimeFrame.FifteenMinutes
}.WithInterval(new Interval<DateTime>(today, today)));

Assert.Equal(Price, history.BaseValue);

Expand Down
2 changes: 2 additions & 0 deletions Alpaca.Markets.sln.DotSettings
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/CodeStyle/Naming/CSharpNaming/ApplyAutoDetectedRules/@EntryValue">False</s:Boolean>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticReadonly/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=15b5b1f1_002D457c_002D4ca6_002Db278_002D5615aedc07d3/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static readonly fields (private)"&gt;&lt;ElementKinds&gt;&lt;Kind Name="READONLY_FIELD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/UserRules/=9ce08e90_002D9663_002D4744_002Daf52_002D9a71b66ba559/@EntryIndexedValue">&lt;Policy&gt;&lt;Descriptor Staticness="Static, Instance" AccessRightKinds="Private" Description="Private methods"&gt;&lt;ElementKinds&gt;&lt;Kind Name="METHOD" /&gt;&lt;/ElementKinds&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EPredefinedNamingRulesToUserRulesUpgrade/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Housekeeping/ExcludedProjects/ProjectMasksToIgnore/=Documentation/@EntryIndexedValue">False</s:Boolean>
<s:Boolean x:Key="/Default/Housekeeping/ExcludedProjects/ProjectMasksToIgnore/=Documentation/@EntryIndexRemoved">True</s:Boolean>
<s:Boolean x:Key="/Default/Housekeeping/ExcludedProjects/ProjectMasksToIgnore/=Documentation_002Ecsproj/@EntryIndexedValue">True</s:Boolean>
Expand Down
2 changes: 1 addition & 1 deletion Alpaca.Markets/Alpaca.Markets.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@

<PropertyGroup>
<EnableStrictModeForCompatibleFrameworksInPackage>true</EnableStrictModeForCompatibleFrameworksInPackage>
<GenerateCompatibilitySuppressionFile>true</GenerateCompatibilitySuppressionFile>
<!--<GenerateCompatibilitySuppressionFile>true</GenerateCompatibilitySuppressionFile>-->
<PackageValidationBaselineVersion>7.0.0</PackageValidationBaselineVersion>
<EnableStrictModeForCompatibleTfms>true</EnableStrictModeForCompatibleTfms>
<EnablePackageValidation>true</EnablePackageValidation>
Expand Down
2 changes: 1 addition & 1 deletion Alpaca.Markets/AlpacaTradingClient.General.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public async Task<IPortfolioHistory> GetPortfolioHistoryAsync(
PortfolioHistoryRequest request,
CancellationToken cancellationToken = default) =>
await _httpClient.GetAsync<IPortfolioHistory, JsonPortfolioHistory>(
await request.EnsureNotNull()
await request.EnsureNotNull().Validate()
.GetUriBuilderAsync(_httpClient).ConfigureAwait(false),
_rateLimitHandler, cancellationToken).ConfigureAwait(false);

Expand Down
28 changes: 0 additions & 28 deletions Alpaca.Markets/CompatibilitySuppressions.xml
Original file line number Diff line number Diff line change
Expand Up @@ -575,13 +575,6 @@
<Right>lib/net462/Alpaca.Markets.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Alpaca.Markets.PortfolioHistoryRequest.WithInterval(Alpaca.Markets.Interval{System.DateTime})</Target>
<Left>lib/net462/Alpaca.Markets.dll</Left>
<Right>lib/net462/Alpaca.Markets.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>F:Alpaca.Markets.AccountActivityType.RefTafFee</Target>
Expand Down Expand Up @@ -876,13 +869,6 @@
<Right>lib/net6.0/Alpaca.Markets.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Alpaca.Markets.PortfolioHistoryRequest.WithInterval(Alpaca.Markets.Interval{System.DateTime})</Target>
<Left>lib/net6.0/Alpaca.Markets.dll</Left>
<Right>lib/net6.0/Alpaca.Markets.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>F:Alpaca.Markets.AccountActivityType.RefTafFee</Target>
Expand Down Expand Up @@ -1177,13 +1163,6 @@
<Right>lib/netstandard2.0/Alpaca.Markets.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Alpaca.Markets.PortfolioHistoryRequest.WithInterval(Alpaca.Markets.Interval{System.DateTime})</Target>
<Left>lib/netstandard2.0/Alpaca.Markets.dll</Left>
<Right>lib/netstandard2.0/Alpaca.Markets.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>F:Alpaca.Markets.AccountActivityType.RefTafFee</Target>
Expand Down Expand Up @@ -1478,13 +1457,6 @@
<Right>lib/netstandard2.1/Alpaca.Markets.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Alpaca.Markets.PortfolioHistoryRequest.WithInterval(Alpaca.Markets.Interval{System.DateTime})</Target>
<Left>lib/netstandard2.1/Alpaca.Markets.dll</Left>
<Right>lib/netstandard2.1/Alpaca.Markets.dll</Right>
<IsBaselineSuppression>true</IsBaselineSuppression>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Alpaca.Markets.IAlpacaTradingClient.ExerciseOptionsPositionByIdAsync(System.Guid,System.Threading.CancellationToken)</Target>
Expand Down
22 changes: 22 additions & 0 deletions Alpaca.Markets/Enums/IntradayProfitLoss.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace Alpaca.Markets;

/// <summary>
/// Intraday profit/loss calculation for portfolio history in the Alpaca REST API.
/// </summary>
[JsonConverter(typeof(StringEnumConverter))]
public enum IntradayProfitLoss
{
/// <summary>
/// Don't reset the profit/los value to the previous day's closing equity for each trading day.
/// </summary>
[UsedImplicitly]
[EnumMember(Value = "no_reset")]
NoReset,

/// <summary>
/// Reset the profit/los value to the previous day's closing equity for each trading day.
/// </summary>
[UsedImplicitly]
[EnumMember(Value = "per_day")]
PerDay
}
31 changes: 31 additions & 0 deletions Alpaca.Markets/Enums/IntradayReporting.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
namespace Alpaca.Markets;

/// <summary>
/// Intraday reporting styles for portfolio history in the Alpaca REST API.
/// </summary>
[JsonConverter(typeof(StringEnumConverter))]
public enum IntradayReporting
{
/// <summary>
/// Only timestamps for the core equity trading hours are returned (usually 9:30am to 4:00pm, trading days only).
/// </summary>
[UsedImplicitly]
[EnumMember(Value = "market_hours")]
MarketHours,

/// <summary>
/// Returns timestamps for the whole session including extended hours (usually 4:00am to 8:00pm, trading days only).
/// </summary>
[UsedImplicitly]
[EnumMember(Value = "extended_hours")]
ExtendedHours,

/// <summary>
/// Returns price data points 24/7 (for off-session times too). To calculate the equity values we are using the following prices:
/// - Between 4:00am and 10:00pm on trading days the valuation will be calculated based on the last trade (extended hours and normal hours respectively).
/// - After 10:00pm, until the next session open the equities will be valued at their official closing price on the primary exchange.
/// </summary>
[UsedImplicitly]
[EnumMember(Value = "continuous")]
Continuous
}
3 changes: 3 additions & 0 deletions Alpaca.Markets/Interfaces/IAlpacaTradingClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,9 @@ Task<IReadOnlyList<IAccountActivity>> ListAccountActivitiesAsync(
/// </summary>
/// <param name="request">Portfolio history request parameters.</param>
/// <param name="cancellationToken">A cancellation token that can be used by other objects or threads to receive notice of cancellation.</param>
/// <exception cref="RequestValidationException">
/// The <paramref name="request"/> argument contains invalid data or some required data is missing, unable to create a valid HTTP request.
/// </exception>
/// <exception cref="HttpRequestException">
/// The request failed due to an underlying issue such as network connectivity, DNS failure, server certificate validation or timeout.
/// </exception>
Expand Down
59 changes: 53 additions & 6 deletions Alpaca.Markets/Parameters/PortfolioHistoryRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,21 @@
/// Encapsulates request parameters for <see cref="IAlpacaTradingClient.GetPortfolioHistoryAsync(PortfolioHistoryRequest,CancellationToken)"/> call.
/// </summary>
[UsedImplicitly]
public sealed class PortfolioHistoryRequest
public sealed class PortfolioHistoryRequest : Validation.IRequest
{
/// <summary>
/// Gets inclusive date interval for filtering items in response.
/// </summary>
[UsedImplicitly]
public Interval<DateOnly> DateInterval { get; private set; }
[ExcludeFromCodeCoverage]
[Obsolete("Use the Interval property instead of this one.", true)]
public Interval<DateOnly> DateInterval => Interval.AsDateInterval();

/// <summary>
/// Gets inclusive date interval for filtering items in response.
/// </summary>
[UsedImplicitly]
public Interval<DateTime> Interval { get; private set; }

/// <summary>
/// Gets or sets the time frame value for desired history. Default value (if <c>null</c>) is 1 minute
Expand All @@ -25,11 +33,24 @@ public sealed class PortfolioHistoryRequest
[UsedImplicitly]
public HistoryPeriod? Period { get; set; }

/// <summary>
/// Gets or sets intraday reporting style. Make sense only if <see cref="TimeFrame"/> are equal to <see cref="TimeFrame.Day"/>.
/// </summary>
[UsedImplicitly]
public IntradayReporting? IntradayReporting { get; set; }

/// <summary>
/// Gets or sets intraday profit/loss reset. Make sense only if <see cref="TimeFrame"/> are equal to <see cref="TimeFrame.Day"/>.
/// </summary>
[UsedImplicitly]
public IntradayProfitLoss? IntradayProfitLoss { get; set; }

/// <summary>
/// Gets or sets flags, indicating that include extended hours included in the result.
/// This is effective only for time frame less than 1 day.
/// </summary>
[UsedImplicitly]
[Obsolete("Use the DateInterval property instead of this one.", false)]
public Boolean? ExtendedHours { get; set; }

internal async ValueTask<UriBuilder> GetUriBuilderAsync(
Expand All @@ -38,12 +59,13 @@ internal async ValueTask<UriBuilder> GetUriBuilderAsync(
{
Path = "v2/account/portfolio/history",
Query = await new QueryBuilder()
.AddParameter("start_date", DateInterval.From)
.AddParameter("end_date", DateInterval.Into)
.AddParameter("intraday_reporting", IntradayReporting)
.AddParameter("period", Period?.ToString())
.AddParameter("start", Interval.From, "O")
.AddParameter("end", Interval.Into, "O")
.AddParameter("pnl_reset", IntradayProfitLoss)
// ReSharper disable once StringLiteralTypo
.AddParameter("timeframe", TimeFrame)
.AddParameter("extended_hours", ExtendedHours)
.AsStringAsync().ConfigureAwait(false)
};

Expand All @@ -54,10 +76,35 @@ internal async ValueTask<UriBuilder> GetUriBuilderAsync(
/// <returns>Request with applied filtering.</returns>
[UsedImplicitly]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public PortfolioHistoryRequest WithInterval(
Interval<DateTime> value)
{
Interval = value;
return this;
}

/// <summary>
/// Sets time interval for filtering data returned by this request.
/// /// </summary>
/// <param name="value">New filtering interval.</param>
/// <returns>Request with applied filtering.</returns>
[UsedImplicitly]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Obsolete("Use the override that gets Interval<DateTime> instead of this one.", true)]
public PortfolioHistoryRequest WithInterval(
Interval<DateOnly> value)
{
DateInterval = value;
Interval = value.AsTimeInterval();
return this;
}

IEnumerable<RequestValidationException?> Validation.IRequest.GetExceptions()
{
yield return Interval.TryValidateInterval();
if (Period.HasValue && !Interval.IsEmpty())
{
yield return new RequestValidationException(
"Both `Period` and `Interval` are set.", nameof(Period));
}
}
}
12 changes: 12 additions & 0 deletions Alpaca.Markets/PublicAPI.Shipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,13 @@ Alpaca.Markets.INewsArticle.Summary.get -> string!
Alpaca.Markets.INewsArticle.Symbols.get -> System.Collections.Generic.IReadOnlyList<string!>!
Alpaca.Markets.INewsArticle.ThumbImageUrl.get -> System.Uri?
Alpaca.Markets.INewsArticle.UpdatedAtUtc.get -> System.DateTime
Alpaca.Markets.IntradayProfitLoss
Alpaca.Markets.IntradayProfitLoss.NoReset = 0 -> Alpaca.Markets.IntradayProfitLoss
Alpaca.Markets.IntradayProfitLoss.PerDay = 1 -> Alpaca.Markets.IntradayProfitLoss
Alpaca.Markets.IntradayReporting
Alpaca.Markets.IntradayReporting.Continuous = 2 -> Alpaca.Markets.IntradayReporting
Alpaca.Markets.IntradayReporting.ExtendedHours = 1 -> Alpaca.Markets.IntradayReporting
Alpaca.Markets.IntradayReporting.MarketHours = 0 -> Alpaca.Markets.IntradayReporting
Alpaca.Markets.IOptionContract
Alpaca.Markets.IOptionContract.ClosePrice.get -> decimal?
Alpaca.Markets.IOptionContract.ClosePriceDate.get -> System.DateOnly?
Expand Down Expand Up @@ -1049,6 +1056,11 @@ Alpaca.Markets.PortfolioHistoryRequest
Alpaca.Markets.PortfolioHistoryRequest.ExtendedHours.get -> bool?
Alpaca.Markets.PortfolioHistoryRequest.ExtendedHours.set -> void
Alpaca.Markets.PortfolioHistoryRequest.DateInterval.get -> Alpaca.Markets.Interval<System.DateOnly>
Alpaca.Markets.PortfolioHistoryRequest.Interval.get -> Alpaca.Markets.Interval<System.DateTime>
Alpaca.Markets.PortfolioHistoryRequest.IntradayProfitLoss.get -> Alpaca.Markets.IntradayProfitLoss?
Alpaca.Markets.PortfolioHistoryRequest.IntradayProfitLoss.set -> void
Alpaca.Markets.PortfolioHistoryRequest.IntradayReporting.get -> Alpaca.Markets.IntradayReporting?
Alpaca.Markets.PortfolioHistoryRequest.IntradayReporting.set -> void
Alpaca.Markets.PortfolioHistoryRequest.Period.get -> Alpaca.Markets.HistoryPeriod?
Alpaca.Markets.PortfolioHistoryRequest.Period.set -> void
Alpaca.Markets.PortfolioHistoryRequest.PortfolioHistoryRequest() -> void
Expand Down
1 change: 1 addition & 0 deletions Alpaca.Markets/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ Alpaca.Markets.OptionContractsRequest.UnderlyingSymbols.get -> System.Collection
Alpaca.Markets.OptionsFeed
Alpaca.Markets.OptionsFeed.Indicative = 1 -> Alpaca.Markets.OptionsFeed
Alpaca.Markets.OptionsFeed.Opra = 0 -> Alpaca.Markets.OptionsFeed
Alpaca.Markets.PortfolioHistoryRequest.WithInterval(Alpaca.Markets.Interval<System.DateTime> value) -> Alpaca.Markets.PortfolioHistoryRequest!

0 comments on commit 988ead9

Please sign in to comment.